superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ccwilli...@apache.org
Subject [incubator-superset] branch master updated: [refactor] Migrate from Mocha+Chai to Jest (#6079)
Date Mon, 15 Oct 2018 20:10:23 GMT
This is an automated email from the ASF dual-hosted git repository.

ccwilliams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 9029701  [refactor] Migrate from Mocha+Chai to Jest (#6079)
9029701 is described below

commit 9029701f2426f3be62da071c499874b7e68f0b84
Author: Christine Chambers <christine.d.hang@gmail.com>
AuthorDate: Mon Oct 15 13:10:18 2018 -0700

    [refactor] Migrate from Mocha+Chai to Jest (#6079)
    
    * [refactor] Migrate from Mocha+Chai to Jest
    
    This change migrates all the existing unit tests
    - to Jest's global expect and matchers from chai's imported expect, asserts and matchers.
    - to Jest's describe/test from mocha's describe/it
    
    The majority of the mechanical changes to tests are achieved through running jest-codemods. The only two note-worthy manual tweaks:
    1. Setting a testURL of http://localhost in jest config and adjusting a few tests to leverage this value instead of relying on about:blank.
    2. Re-enabling ExploreChartPanel_spec which was previously commented out as we cannot have empty tests with nothing in it with Jest. :)
    
    This change also removes dependencies to Mocha and Chai.
    
    * Remove the test:one command as it now does the same thing as test.
    
    * Fixing lint errors. The diff looks large but is large done through `yarn run lint --fix`
    
    The only noteworthy change is the one in eslintrc for tests. The env has been updated from mocha to jest.
    
    * Adding eslint-plugin-jest and further modify tests.
    
    - One small fix in sqllab's Timer Spec for a test that is not using the spy it created for testing.
    - Deletion of a duplicated test caught by eslint-plugin-jest.
    
    * - Make istanbul coverage work with Jest.
    
    - Remove dependency on stand-alone istanbul and babel-istanbul as they're built-into jest. Yes!
    
    * Attempt to fix dynamic imports in tests.
    
    * run sequentially and log heap usage
    
    * - tweaking maxworkers for travis and specifying coverageDirectory for codecov
    
    - remove dynamic import in shim.js now that it is set in babelrc for tests only.
---
 CONTRIBUTING.md                                    |    2 +-
 superset/assets/.babelrc                           |    9 +-
 superset/assets/.eslintrc                          |    8 +-
 superset/assets/jest.config.js                     |   11 +
 superset/assets/package.json                       |   14 +-
 superset/assets/spec/.eslintrc                     |   11 +-
 superset/assets/spec/__mocks__/fileMock.js         |    1 +
 superset/assets/spec/__mocks__/styleMock.js        |    1 +
 superset/assets/spec/helpers/shim.js               |   11 -
 .../spec/javascripts/CRUD/CollectionTable_spec.jsx |    7 +-
 .../addSlice/AddSliceContainer_spec.jsx            |   13 +-
 .../assets/spec/javascripts/chart/Chart_spec.jsx   |   11 +-
 .../components/AlteredSliceTag_spec.jsx            |   69 +-
 .../javascripts/components/AsyncSelect_spec.jsx    |   17 +-
 .../javascripts/components/CachedLabel_spec.jsx    |    5 +-
 .../spec/javascripts/components/Checkbox_spec.jsx  |   13 +-
 .../javascripts/components/ColumnOption_spec.jsx   |   25 +-
 .../components/ColumnTypeLabel_spec.jsx            |   27 +-
 .../components/CopyToClipboard_spec.jsx            |    3 +-
 .../FilterableTable/FilterableTable_spec.jsx       |   11 +-
 .../javascripts/components/MetricOption_spec.jsx   |   21 +-
 .../javascripts/components/ModalTrigger_spec.jsx   |    3 +-
 .../javascripts/components/OnPasteSelect_spec.jsx  |   27 +-
 .../components/OptionDescription_spec.jsx          |    5 +-
 .../javascripts/components/PopoverSection_spec.jsx |    7 +-
 .../components/URLShortLinkButton_spec.jsx         |    3 +-
 .../components/URLShortLinkModal_spec.jsx          |    3 +-
 .../components/VirtualizedRendererWrap_spec.jsx    |   29 +-
 .../dashboard/actions/dashboardLayout_spec.js      |   99 +-
 .../dashboard/components/CodeModal_spec.jsx        |    5 +-
 .../dashboard/components/CssEditor_spec.jsx        |    5 +-
 .../dashboard/components/DashboardBuilder_spec.jsx |   39 +-
 .../dashboard/components/DashboardGrid_spec.jsx    |   23 +-
 .../dashboard/components/Dashboard_spec.jsx        |   23 +-
 .../components/HeaderActionsDropdown_spec.jsx      |   37 +-
 .../dashboard/components/Header_spec.jsx           |   31 +-
 .../dashboard/components/MissingChart_spec.jsx     |    7 +-
 .../components/RefreshIntervalModal_spec.jsx       |    5 +-
 .../dashboard/components/SliceAdder_spec.jsx       |   33 +-
 .../components/dnd/DragDroppable_spec.jsx          |   25 +-
 .../components/gridComponents/ChartHolder_spec.jsx |   33 +-
 .../components/gridComponents/Chart_spec.jsx       |   15 +-
 .../components/gridComponents/Column_spec.jsx      |   37 +-
 .../components/gridComponents/Divider_spec.jsx     |   15 +-
 .../components/gridComponents/Header_spec.jsx      |   23 +-
 .../components/gridComponents/Markdown_spec.jsx    |   49 +-
 .../components/gridComponents/Row_spec.jsx         |   23 +-
 .../components/gridComponents/Tab_spec.jsx         |   27 +-
 .../components/gridComponents/Tabs_spec.jsx        |   31 +-
 .../new/DraggableNewComponent_spec.jsx             |   11 +-
 .../gridComponents/new/NewColumn_spec.jsx          |    5 +-
 .../gridComponents/new/NewDivider_spec.jsx         |    5 +-
 .../gridComponents/new/NewHeader_spec.jsx          |    5 +-
 .../components/gridComponents/new/NewRow_spec.jsx  |    5 +-
 .../components/gridComponents/new/NewTabs_spec.jsx |    5 +-
 .../dashboard/components/menu/HoverMenu_spec.jsx   |    3 +-
 .../components/menu/WithPopoverMenu_spec.jsx       |   27 +-
 .../resizable/ResizableContainer_spec.jsx          |    3 +-
 .../components/resizable/ResizableHandle_spec.jsx  |    9 +-
 .../dashboard/containers/Dashboard_spec.jsx        |    5 +-
 .../dashboard/reducers/dashboardLayout_spec.js     |   44 +-
 .../dashboard/reducers/dashboardState_spec.js      |   38 +-
 .../dashboard/reducers/sliceEntities_spec.js       |   14 +-
 .../dashboard/util/componentIsResizable_spec.js    |    6 +-
 .../javascripts/dashboard/util/dnd-reorder_spec.js |   10 +-
 .../dashboard/util/dropOverflowsParent_spec.js     |   18 +-
 .../util/findFirstParentContainer_spec.js          |    8 +-
 .../dashboard/util/findParentId_spec.js            |    8 +-
 .../dashboard/util/getChartIdsFromLayout_spec.js   |   12 +-
 .../dashboard/util/getDashboardUrl_spec.js         |    4 +-
 .../util/getDetailedComponentWidth_spec.js         |   42 +-
 .../dashboard/util/getDropPosition_spec.js         |   44 +-
 .../util/getFormDataWithExtraFilters_spec.js       |   12 +-
 .../dashboard/util/isValidChild_spec.js            |    6 +-
 .../dashboard/util/newComponentFactory_spec.js     |   12 +-
 .../dashboard/util/newEntitiesFromDrop_spec.js     |   24 +-
 .../datasource/DatasourceEditor_spec.jsx           |   13 +-
 .../datasource/DatasourceModal_spec.jsx            |    9 +-
 .../spec/javascripts/explore/AdhocFilter_spec.js   |   40 +-
 .../spec/javascripts/explore/AdhocMetric_spec.js   |  122 +--
 .../spec/javascripts/explore/chartActions_spec.js  |    5 +-
 .../explore/components/AdhocFilterControl_spec.jsx |   23 +-
 ...AdhocFilterEditPopoverSimpleTabContent_spec.jsx |   39 +-
 .../AdhocFilterEditPopoverSqlTabContent_spec.jsx   |   11 +-
 .../components/AdhocFilterEditPopover_spec.jsx     |   39 +-
 .../explore/components/AdhocFilterOption_spec.jsx  |    5 +-
 .../AdhocMetricEditPopoverTitle_spec.jsx           |    9 +-
 .../components/AdhocMetricEditPopover_spec.jsx     |   39 +-
 .../explore/components/AdhocMetricOption_spec.jsx  |    5 +-
 .../components/AdhocMetricStaticOption_spec.jsx    |    3 +-
 .../explore/components/AggregateOption_spec.jsx    |    3 +-
 .../explore/components/BoundsControl_spec.jsx      |    9 +-
 .../explore/components/CheckboxControl_spec.jsx    |    5 +-
 .../explore/components/ColorPickerControl_spec.jsx |   13 +-
 .../explore/components/ColorScheme_spec.jsx        |    3 +-
 .../components/ControlPanelSection_spec.jsx        |   10 +-
 .../components/ControlPanelsContainer_spec.jsx     |    3 +-
 .../explore/components/ControlRow_spec.jsx         |    9 +-
 .../explore/components/DatasourceControl_spec.jsx  |    3 +-
 .../explore/components/DateFilterControl_spec.jsx  |   13 +-
 .../explore/components/DisplayQueryButton_spec.jsx |    5 +-
 .../explore/components/EmbedCodeButton_spec.jsx    |    9 +-
 .../components/ExploreActionButtons_spec.jsx       |    5 +-
 .../explore/components/ExploreChartHeader_spec.jsx |    7 +-
 .../explore/components/ExploreChartPanel_spec.js   |   21 -
 .../explore/components/ExploreChartPanel_spec.jsx  |   17 +
 .../components/ExploreViewContainer_spec.jsx       |   13 +-
 .../components/FilterDefinitionOption_spec.jsx     |    7 +-
 .../components/FixedOrMetricControl_spec.jsx       |    9 +-
 .../components/MetricDefinitionOption_spec.jsx     |    7 +-
 .../components/MetricDefinitionValue_spec.jsx      |    5 +-
 .../explore/components/MetricsControl_spec.jsx     |   57 +-
 .../explore/components/QueryAndSaveBtns_spec.jsx   |   11 +-
 .../explore/components/RowCountLabel_spec.jsx      |    9 +-
 .../components/RunQueryActionButton_spec.jsx       |    5 +-
 .../explore/components/SaveModal_spec.jsx          |   58 +-
 .../explore/components/SelectControl_spec.jsx      |   21 +-
 .../explore/components/TextArea_spec.jsx           |    9 +-
 .../components/TimeSeriesColumnControl_spec.jsx    |    5 +-
 .../explore/components/ViewportControl_spec.jsx    |    9 +-
 .../explore/components/VizTypeControl_spec.jsx     |    9 +-
 .../javascripts/explore/exploreActions_spec.js     |    5 +-
 .../assets/spec/javascripts/explore/utils_spec.jsx |   21 +-
 superset/assets/spec/javascripts/logger_spec.js    |  156 +--
 .../components/ToastPresenter_spec.jsx             |    7 +-
 .../messageToasts/components/Toast_spec.jsx        |    9 +-
 .../messageToasts/reducers/messageToasts_spec.js   |   10 +-
 .../utils/getToastsFromPyFlashMessages_spec.js     |    8 +-
 .../modules/CategoricalColorNameSpace_spec.js      |  166 +--
 .../modules/CategoricalColorScale_spec.js          |   36 +-
 .../javascripts/modules/ColorSchemeManager_spec.js |   44 +-
 .../spec/javascripts/modules/Registry_spec.js      |  154 +--
 .../spec/javascripts/modules/colors_spec.jsx       |   13 +-
 .../assets/spec/javascripts/modules/dates_spec.js  |   45 +-
 .../assets/spec/javascripts/modules/geo_spec.jsx   |   16 +-
 .../spec/javascripts/modules/sandbox_spec.jsx      |   10 +-
 .../assets/spec/javascripts/modules/time_spec.js   |   26 +-
 .../assets/spec/javascripts/modules/utils_spec.jsx |   61 +-
 .../assets/spec/javascripts/profile/App_spec.jsx   |    9 +-
 .../javascripts/profile/CreatedContent_spec.jsx    |    7 +-
 .../javascripts/profile/EditableTitle_spec.jsx     |   33 +-
 .../spec/javascripts/profile/Favorites_spec.jsx    |    7 +-
 .../javascripts/profile/RecentActivity_spec.jsx    |    5 +-
 .../spec/javascripts/profile/Security_spec.jsx     |   13 +-
 .../spec/javascripts/profile/UserInfo_spec.jsx     |   13 +-
 .../assets/spec/javascripts/sqllab/App_spec.jsx    |   13 +-
 .../spec/javascripts/sqllab/ColumnElement_spec.jsx |   17 +-
 .../javascripts/sqllab/CopyQueryTabUrl_spec.jsx    |    3 +-
 .../sqllab/ExploreResultsButton_spec.jsx           |   37 +-
 .../javascripts/sqllab/HighlightedSql_spec.jsx     |   12 +-
 .../assets/spec/javascripts/sqllab/Link_spec.jsx   |    7 +-
 .../spec/javascripts/sqllab/QuerySearch_spec.jsx   |   23 +-
 .../javascripts/sqllab/QueryStateLabel_spec.jsx    |    5 +-
 .../spec/javascripts/sqllab/QueryTable_spec.jsx    |   11 +-
 .../spec/javascripts/sqllab/ResultSet_spec.jsx     |   31 +-
 .../spec/javascripts/sqllab/SaveQuery_spec.jsx     |   11 +-
 .../javascripts/sqllab/SqlEditorLeftBar_spec.jsx   |   45 +-
 .../spec/javascripts/sqllab/SqlEditor_spec.jsx     |    5 +-
 .../spec/javascripts/sqllab/TabStatusIcon_spec.jsx |   11 +-
 .../javascripts/sqllab/TabbedSqlEditors_spec.jsx   |   38 +-
 .../spec/javascripts/sqllab/TableElement_spec.jsx  |   27 +-
 .../assets/spec/javascripts/sqllab/Timer_spec.jsx  |   18 +-
 .../assets/spec/javascripts/sqllab/actions_spec.js |   35 +-
 .../spec/javascripts/sqllab/reducers_spec.js       |   52 +-
 .../assets/spec/javascripts/utils/common_spec.jsx  |   59 +-
 .../utils/convertKeysToCamelCase_spec.js           |   14 +-
 .../spec/javascripts/utils/isDefined_spec.js       |   26 +-
 .../spec/javascripts/utils/isRequired_spec.js      |    4 +-
 .../spec/javascripts/utils/makeSingleton_spec.js   |   30 +-
 .../visualizations/models/ChartPlugin_spec.js      |   16 +-
 .../visualizations/models/Plugin_spec.js           |   42 +-
 .../visualizations/models/Preset_spec.js           |   10 +-
 .../javascripts/visualizations/nvd3/utils_spec.js  |   28 +-
 .../spec/javascripts/visualizations/table_spec.jsx |   19 +-
 .../javascripts/welcome/DashboardTable_spec.jsx    |    7 +-
 .../spec/javascripts/welcome/Welcome_spec.jsx      |    9 +-
 superset/assets/yarn.lock                          | 1118 +++++++++++++++-----
 177 files changed, 2549 insertions(+), 2176 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e7d1034..da9ca53 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -327,7 +327,7 @@ commands are invoked.
 
 ### JavaScript testing
 
-We use [Mocha](https://mochajs.org/), [Chai](http://chaijs.com/) and [Enzyme](http://airbnb.io/enzyme/) to test Javascript. Tests can be run with:
+We use [Jest](https://jestjs.io/) and [Enzyme](http://airbnb.io/enzyme/) to test Javascript. Tests can be run with:
 
 ```bash
 cd superset/assets/spec
diff --git a/superset/assets/.babelrc b/superset/assets/.babelrc
index c2ea3fd..d3cf36d 100644
--- a/superset/assets/.babelrc
+++ b/superset/assets/.babelrc
@@ -1,4 +1,11 @@
 {
   "presets" : ["airbnb", "react", "env"],
-  "plugins": ["lodash", "syntax-dynamic-import", "react-hot-loader/babel"]
+  "plugins": ["lodash", "syntax-dynamic-import", "react-hot-loader/babel"],
+  "env": {
+    "test": {
+      "plugins": [
+        "babel-plugin-dynamic-import-node"
+      ]
+    }
+  }
 }
diff --git a/superset/assets/.eslintrc b/superset/assets/.eslintrc
index 7c078cd..fde2e65 100644
--- a/superset/assets/.eslintrc
+++ b/superset/assets/.eslintrc
@@ -40,10 +40,6 @@
     "react/no-string-refs": 0,
     "indent": 0,
     "no-multi-spaces": 0,
-    "padded-blocks": 0,
-    "no-only-tests/no-only-tests": 2
-  },
-  "plugins": [
-    "no-only-tests"
-  ]
+    "padded-blocks": 0
+  }
 }
diff --git a/superset/assets/jest.config.js b/superset/assets/jest.config.js
new file mode 100644
index 0000000..999cdb7
--- /dev/null
+++ b/superset/assets/jest.config.js
@@ -0,0 +1,11 @@
+module.exports = {
+  testRegex: '\\/spec\\/.*_spec\\.jsx?$',
+  moduleNameMapper: {
+    '\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js',
+    '\\.(gif|ttf|eot|svg)$': '<rootDir>/spec/__mocks__/fileMock.js',
+  },
+  setupTestFrameworkScriptFile: '<rootDir>/spec/helpers/shim.js',
+  testURL: 'http://localhost',
+  collectCoverageFrom: ['src/**/*.{js,jsx}'],
+  coverageDirectory: '<rootDir>/coverage/',
+};
diff --git a/superset/assets/package.json b/superset/assets/package.json
index cdb7d2c..df297e4 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -8,10 +8,9 @@
     "test": "spec"
   },
   "scripts": {
-    "tdd": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js 'spec/**/*_spec.*' --watch --recursive",
-    "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.*'",
+    "tdd": "jest --watch",
+    "test": "jest",
+    "cover": "jest --maxWorkers=8 --coverage",
     "dev": "webpack --mode=development --colors --progress --debug --watch",
     "dev-server": "webpack-dev-server --mode=development --progress",
     "prod": "node --max_old_space_size=4096 webpack --mode=production --colors --progress",
@@ -134,7 +133,7 @@
     "babel-cli": "^6.14.0",
     "babel-core": "^6.10.4",
     "babel-eslint": "^8.2.2",
-    "babel-istanbul": "^0.12.2",
+    "babel-jest": "^23.6.0",
     "babel-loader": "^7.1.4",
     "babel-plugin-css-modules-transform": "^1.1.0",
     "babel-plugin-dynamic-import-node": "^1.2.0",
@@ -143,7 +142,6 @@
     "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": "^1.0.0",
     "cypress": "^3.0.3",
@@ -154,6 +152,7 @@
     "eslint-config-prettier": "^2.9.0",
     "eslint-plugin-cypress": "^2.0.1",
     "eslint-plugin-import": "^2.2.0",
+    "eslint-plugin-jest": "^21.24.1",
     "eslint-plugin-jsx-a11y": "^5.1.1",
     "eslint-plugin-no-only-tests": "^2.0.1",
     "eslint-plugin-prettier": "^2.6.0",
@@ -163,13 +162,12 @@
     "gl": "^4.0.4",
     "ignore-styles": "^5.0.1",
     "imports-loader": "^0.7.1",
-    "istanbul": "^1.0.0-alpha",
+    "jest": "^23.6.0",
     "jsdom": "9.12.0",
     "less": "^2.6.1",
     "less-loader": "^4.1.0",
     "mini-css-extract-plugin": "^0.4.0",
     "minimist": "^1.2.0",
-    "mocha": "^3.5.3",
     "npm-check-updates": "^2.14.2",
     "optimize-css-assets-webpack-plugin": "^5.0.1",
     "po2json": "^0.4.5",
diff --git a/superset/assets/spec/.eslintrc b/superset/assets/spec/.eslintrc
index 5b4214b..28c59ef 100644
--- a/superset/assets/spec/.eslintrc
+++ b/superset/assets/spec/.eslintrc
@@ -1,8 +1,15 @@
 {
+  "plugins": [
+    "jest",
+    "no-only-tests"
+  ],
   "env": {
-    "mocha": true
+    "jest/globals": true
   },
+  "extends": ["plugin:jest/recommended"],
   "rules": {
-    "import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
+    "import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
+    "jest/consistent-test-it": "error",
+    "no-only-tests/no-only-tests": "error"
   }
 }
diff --git a/superset/assets/spec/__mocks__/fileMock.js b/superset/assets/spec/__mocks__/fileMock.js
new file mode 100644
index 0000000..86059f3
--- /dev/null
+++ b/superset/assets/spec/__mocks__/fileMock.js
@@ -0,0 +1 @@
+module.exports = 'test-file-stub';
diff --git a/superset/assets/spec/__mocks__/styleMock.js b/superset/assets/spec/__mocks__/styleMock.js
new file mode 100644
index 0000000..f053ebf
--- /dev/null
+++ b/superset/assets/spec/__mocks__/styleMock.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/superset/assets/spec/helpers/shim.js b/superset/assets/spec/helpers/shim.js
index 6decdc9..306d35a 100644
--- a/superset/assets/spec/helpers/shim.js
+++ b/superset/assets/spec/helpers/shim.js
@@ -1,19 +1,11 @@
 /* eslint no-native-reassign: 0 */
 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
-  // `syntax-dynamic-import` it breaks webpack's bundle splitting capability.
-  // So only load during runtime on the node-side (in tests)
-  plugins: ['dynamic-import-node'],
-});
-
 const exposedProperties = ['window', 'navigator', 'document'];
 
 global.jsdom = jsdom.jsdom;
@@ -46,9 +38,6 @@ global.XMLHttpRequest = global.window.XMLHttpRequest;
 
 global.sinon = require('sinon');
 
-global.expect = chai.expect;
-global.assert = chai.assert;
-
 global.sinon.useFakeXMLHttpRequest();
 
 global.window.XMLHttpRequest = global.XMLHttpRequest;
diff --git a/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx b/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx
index 20bc359..a52ecb3 100644
--- a/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx
+++ b/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import CollectionTable from '../../../src/CRUD/CollectionTable';
@@ -21,13 +20,13 @@ describe('CollectionTable', () => {
   });
 
   it('is valid', () => {
-    expect(React.isValidElement(el)).to.equal(true);
+    expect(React.isValidElement(el)).toBe(true);
   });
 
   it('renders a table', () => {
     const length = mockDatasource['7__table'].columns.length;
-    expect(wrapper.find('table')).to.have.lengthOf(1);
-    expect(wrapper.find('tbody tr.row')).to.have.lengthOf(length);
+    expect(wrapper.find('table')).toHaveLength(1);
+    expect(wrapper.find('tbody tr.row')).toHaveLength(length);
   });
 
 });
diff --git a/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx b/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx
index de242ba..50a3b2e 100644
--- a/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx
+++ b/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Button } from 'react-bootstrap';
 import Select from 'react-virtualized-select';
@@ -20,19 +19,19 @@ describe('AddSliceContainer', () => {
   });
 
   it('uses table as default visType', () => {
-    expect(wrapper.state().visType).to.equal('table');
+    expect(wrapper.state().visType).toBe('table');
   });
 
   it('renders 2 selects', () => {
-    expect(wrapper.find(Select)).to.have.lengthOf(2);
+    expect(wrapper.find(Select)).toHaveLength(2);
   });
 
   it('renders a button', () => {
-    expect(wrapper.find(Button)).to.have.lengthOf(1);
+    expect(wrapper.find(Button)).toHaveLength(1);
   });
 
   it('renders a disabled button if no datasource is selected', () => {
-    expect(wrapper.find(Button).dive().find('.btn[disabled=true]')).to.have.length(1);
+    expect(wrapper.find(Button).dive().find('.btn[disabled=true]')).toHaveLength(1);
   });
 
   it('renders an enabled button if datasource is selected', () => {
@@ -42,7 +41,7 @@ describe('AddSliceContainer', () => {
       datasourceId: datasourceValue.split('__')[0],
       datasourceType: datasourceValue.split('__')[1],
     });
-    expect(wrapper.find(Button).dive().find('.btn[disabled=false]')).to.have.length(1);
+    expect(wrapper.find(Button).dive().find('.btn[disabled=false]')).toHaveLength(1);
   });
 
   it('formats explore url', () => {
@@ -53,6 +52,6 @@ describe('AddSliceContainer', () => {
       datasourceType: datasourceValue.split('__')[1],
     });
     const formattedUrl = '/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221__table%22%7D';
-    expect(wrapper.instance().exploreUrl()).to.equal(formattedUrl);
+    expect(wrapper.instance().exploreUrl()).toBe(formattedUrl);
   });
 });
diff --git a/superset/assets/spec/javascripts/chart/Chart_spec.jsx b/superset/assets/spec/javascripts/chart/Chart_spec.jsx
index e5c0bb9..0fa440f 100644
--- a/superset/assets/spec/javascripts/chart/Chart_spec.jsx
+++ b/superset/assets/spec/javascripts/chart/Chart_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import { chart as initChart } from '../../../src/chart/chartReducer';
@@ -52,7 +51,7 @@ describe('Chart', () => {
         height: 100,
       });
       wrapper.instance().componentDidUpdate(prevProp);
-      expect(stub.callCount).to.equals(0);
+      expect(stub.callCount).toBe(0);
     });
 
     it('should call after chart stop loading', () => {
@@ -61,7 +60,7 @@ describe('Chart', () => {
         chartStatus: 'success',
       });
       wrapper.instance().componentDidUpdate(prevProp);
-      expect(stub.callCount).to.equals(1);
+      expect(stub.callCount).toBe(1);
     });
 
     it('should call after resize', () => {
@@ -69,14 +68,14 @@ describe('Chart', () => {
         chartStatus: 'rendered',
         height: 100,
       });
-      expect(stub.callCount).to.equals(1);
+      expect(stub.callCount).toBe(1);
     });
   });
 
   describe('render', () => {
     it('should render ChartBody after loading is completed', () => {
-      expect(wrapper.find(Loading)).to.have.length(1);
-      expect(wrapper.find(ChartBody)).to.have.length(0);
+      expect(wrapper.find(Loading)).toHaveLength(1);
+      expect(wrapper.find(ChartBody)).toHaveLength(0);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx b/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx
index 316ac34..399b5a3 100644
--- a/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx
+++ b/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import { Table, Thead, Td, Th, Tr } from 'reactable';
 
@@ -105,9 +104,9 @@ describe('AlteredSliceTag', () => {
 
   it('correctly determines form data differences', () => {
     const diffs = wrapper.instance().getDiffs(props);
-    expect(diffs).to.deep.equal(expectedDiffs);
-    expect(wrapper.instance().state.diffs).to.deep.equal(expectedDiffs);
-    expect(wrapper.instance().state.hasDiffs).to.equal(true);
+    expect(diffs).toEqual(expectedDiffs);
+    expect(wrapper.instance().state.diffs).toEqual(expectedDiffs);
+    expect(wrapper.instance().state.hasDiffs).toBe(true);
   });
 
   it('does not run when there are no differences', () => {
@@ -116,9 +115,9 @@ describe('AlteredSliceTag', () => {
       currentFormData: props.origFormData,
     };
     wrapper = shallow(<AlteredSliceTag {...props} />);
-    expect(wrapper.instance().state.diffs).to.deep.equal({});
-    expect(wrapper.instance().state.hasDiffs).to.equal(false);
-    expect(wrapper.instance().render()).to.equal(null);
+    expect(wrapper.instance().state.diffs).toEqual({});
+    expect(wrapper.instance().state.hasDiffs).toBe(false);
+    expect(wrapper.instance().render()).toBeNull();
   });
 
   it('sets new diffs when receiving new props', () => {
@@ -131,59 +130,59 @@ describe('AlteredSliceTag', () => {
     wrapper.instance().componentWillReceiveProps(newProps);
     const newDiffs = wrapper.instance().state.diffs;
     const expectedBeta = { before: undefined, after: 10 };
-    expect(newDiffs.beta).to.deep.equal(expectedBeta);
+    expect(newDiffs.beta).toEqual(expectedBeta);
   });
 
   it('does not set new state when props are the same', () => {
     const currentDiff = wrapper.instance().state.diffs;
     wrapper.instance().componentWillReceiveProps(props);
     // Check equal references
-    expect(wrapper.instance().state.diffs).to.equal(currentDiff);
+    expect(wrapper.instance().state.diffs).toBe(currentDiff);
   });
 
   it('renders a ModalTrigger', () => {
-    expect(wrapper.find(ModalTrigger)).to.have.lengthOf(1);
+    expect(wrapper.find(ModalTrigger)).toHaveLength(1);
   });
 
   describe('renderTriggerNode', () => {
     it('renders a TooltipWrapper', () => {
       const triggerNode = shallow(<div>{wrapper.instance().renderTriggerNode()}</div>);
-      expect(triggerNode.find(TooltipWrapper)).to.have.lengthOf(1);
+      expect(triggerNode.find(TooltipWrapper)).toHaveLength(1);
     });
   });
 
   describe('renderModalBody', () => {
     it('renders a Table', () => {
       const modalBody = shallow(<div>{wrapper.instance().renderModalBody()}</div>);
-      expect(modalBody.find(Table)).to.have.lengthOf(1);
+      expect(modalBody.find(Table)).toHaveLength(1);
     });
 
     it('renders a Thead', () => {
       const modalBody = shallow(<div>{wrapper.instance().renderModalBody()}</div>);
-      expect(modalBody.find(Thead)).to.have.lengthOf(1);
+      expect(modalBody.find(Thead)).toHaveLength(1);
     });
 
     it('renders Th', () => {
       const modalBody = shallow(<div>{wrapper.instance().renderModalBody()}</div>);
       const th = modalBody.find(Th);
-      expect(th).to.have.lengthOf(3);
+      expect(th).toHaveLength(3);
       ['control', 'before', 'after'].forEach((v, i) => {
-        expect(th.get(i).props.column).to.equal(v);
+        expect(th.get(i).props.column).toBe(v);
       });
     });
 
     it('renders the correct number of Tr', () => {
       const modalBody = shallow(<div>{wrapper.instance().renderModalBody()}</div>);
       const tr = modalBody.find(Tr);
-      expect(tr).to.have.lengthOf(7);
+      expect(tr).toHaveLength(7);
     });
 
     it('renders the correct number of Td', () => {
       const modalBody = shallow(<div>{wrapper.instance().renderModalBody()}</div>);
       const td = modalBody.find(Td);
-      expect(td).to.have.lengthOf(21);
+      expect(td).toHaveLength(21);
       ['control', 'before', 'after'].forEach((v, i) => {
-        expect(td.get(i).props.column).to.equal(v);
+        expect(td.get(i).props.column).toBe(v);
       });
     });
   });
@@ -191,58 +190,56 @@ describe('AlteredSliceTag', () => {
   describe('renderRows', () => {
     it('returns an array of rows with one Tr and three Td', () => {
       const rows = wrapper.instance().renderRows();
-      expect(rows).to.have.lengthOf(7);
+      expect(rows).toHaveLength(7);
       const fakeRow = shallow(<div>{rows[0]}</div>);
-      expect(fakeRow.find(Tr)).to.have.lengthOf(1);
-      expect(fakeRow.find(Td)).to.have.lengthOf(3);
+      expect(fakeRow.find(Tr)).toHaveLength(1);
+      expect(fakeRow.find(Td)).toHaveLength(3);
     });
   });
 
   describe('formatValue', () => {
     it('returns "N/A" for undefined values', () => {
-      expect(wrapper.instance().formatValue(undefined, 'b')).to.equal('N/A');
+      expect(wrapper.instance().formatValue(undefined, 'b')).toBe('N/A');
     });
 
     it('returns "null" for null values', () => {
-      expect(wrapper.instance().formatValue(null, 'b')).to.equal('null');
+      expect(wrapper.instance().formatValue(null, 'b')).toBe('null');
     });
 
     it('returns "Max" and "Min" for BoundsControl', () => {
-      expect(wrapper.instance().formatValue([5, 6], 'y_axis_bounds')).to.equal(
-        'Min: 5, Max: 6',
-      );
+      expect(wrapper.instance().formatValue([5, 6], 'y_axis_bounds')).toBe('Min: 5, Max: 6');
     });
 
     it('returns stringified objects for CollectionControl', () => {
       const value = [{ 1: 2, alpha: 'bravo' }, { sent: 'imental', w0ke: 5 }];
       const expected = '{"1":2,"alpha":"bravo"}, {"sent":"imental","w0ke":5}';
-      expect(wrapper.instance().formatValue(value, 'column_collection')).to.equal(expected);
+      expect(wrapper.instance().formatValue(value, 'column_collection')).toBe(expected);
     });
 
     it('returns boolean values as string', () => {
-      expect(wrapper.instance().formatValue(true, 'b')).to.equal('true');
-      expect(wrapper.instance().formatValue(false, 'b')).to.equal('false');
+      expect(wrapper.instance().formatValue(true, 'b')).toBe('true');
+      expect(wrapper.instance().formatValue(false, 'b')).toBe('false');
     });
 
     it('returns Array joined by commas', () => {
       const value = [5, 6, 7, 8, 'hello', 'goodbye'];
       const expected = '5, 6, 7, 8, hello, goodbye';
-      expect(wrapper.instance().formatValue(value)).to.equal(expected);
+      expect(wrapper.instance().formatValue(value)).toBe(expected);
     });
 
     it('stringifies objects', () => {
       const value = { 1: 2, alpha: 'bravo' };
       const expected = '{"1":2,"alpha":"bravo"}';
-      expect(wrapper.instance().formatValue(value)).to.equal(expected);
+      expect(wrapper.instance().formatValue(value)).toBe(expected);
     });
 
     it('does nothing to strings and numbers', () => {
-      expect(wrapper.instance().formatValue(5)).to.equal(5);
-      expect(wrapper.instance().formatValue('hello')).to.equal('hello');
+      expect(wrapper.instance().formatValue(5)).toBe(5);
+      expect(wrapper.instance().formatValue('hello')).toBe('hello');
     });
 
     it('returns "[]" for empty filters', () => {
-      expect(wrapper.instance().formatValue([], 'adhoc_filters')).to.equal('[]');
+      expect(wrapper.instance().formatValue([], 'adhoc_filters')).toBe('[]');
     });
 
     it('correctly formats filters with array values', () => {
@@ -263,7 +260,7 @@ describe('AlteredSliceTag', () => {
         },
       ];
       const expected = 'a in [1, g, 7, ho], b not in [hu, ho, ha]';
-      expect(wrapper.instance().formatValue(filters, 'adhoc_filters')).to.equal(expected);
+      expect(wrapper.instance().formatValue(filters, 'adhoc_filters')).toBe(expected);
     });
 
     it('correctly formats filters with string values', () => {
@@ -284,7 +281,7 @@ describe('AlteredSliceTag', () => {
         },
       ];
       const expected = 'a == gucci, b LIKE moshi moshi';
-      expect(wrapper.instance().formatValue(filters, 'adhoc_filters')).to.equal(expected);
+      expect(wrapper.instance().formatValue(filters, 'adhoc_filters')).toBe(expected);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx b/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx
index 7401eae..df62c7f 100644
--- a/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx
+++ b/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import Select from 'react-select';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import AsyncSelect from '../../../src/components/AsyncSelect';
@@ -20,14 +19,14 @@ describe('AsyncSelect', () => {
   it('is valid element', () => {
     expect(
       React.isValidElement(<AsyncSelect {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('has one select', () => {
     const wrapper = shallow(
       <AsyncSelect {...mockedProps} />,
     );
-    expect(wrapper.find(Select)).to.have.length(1);
+    expect(wrapper.find(Select)).toHaveLength(1);
   });
 
   it('calls onChange on select change', () => {
@@ -35,7 +34,7 @@ describe('AsyncSelect', () => {
       <AsyncSelect {...mockedProps} />,
     );
     wrapper.find(Select).simulate('change', { value: 1 });
-    expect(mockedProps.onChange).to.have.property('callCount', 1);
+    expect(mockedProps.onChange).toHaveProperty('callCount', 1);
   });
 
   describe('auto select', () => {
@@ -55,7 +54,7 @@ describe('AsyncSelect', () => {
       );
       wrapper.instance().fetchOptions();
       const spy = sinon.spy(wrapper.instance(), 'onChange');
-      expect(spy.callCount).to.equal(0);
+      expect(spy.callCount).toBe(0);
     });
     it('should auto select first option', () => {
       const wrapper = shallow(
@@ -64,8 +63,8 @@ describe('AsyncSelect', () => {
       const spy = sinon.spy(wrapper.instance(), 'onChange');
       server.respond();
 
-      expect(spy.callCount).to.equal(1);
-      expect(spy.calledWith(wrapper.instance().state.options[0])).to.equal(true);
+      expect(spy.callCount).toBe(1);
+      expect(spy.calledWith(wrapper.instance().state.options[0])).toBe(true);
     });
     it('should not auto select when value prop is set', () => {
       const wrapper = shallow(
@@ -75,8 +74,8 @@ describe('AsyncSelect', () => {
       wrapper.instance().fetchOptions();
       server.respond();
 
-      expect(spy.callCount).to.equal(0);
-      expect(wrapper.find(Select)).to.have.length(1);
+      expect(spy.callCount).toBe(0);
+      expect(wrapper.find(Select)).toHaveLength(1);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx b/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx
index 8358b49..4e17adf 100644
--- a/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx
+++ b/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Label } from 'react-bootstrap';
 
@@ -14,12 +13,12 @@ describe('CachedLabel', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<CachedLabel {...defaultProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders', () => {
     const wrapper = shallow(
       <CachedLabel {...defaultProps} />,
     );
-    expect(wrapper.find(Label)).to.have.length(1);
+    expect(wrapper.find(Label)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/Checkbox_spec.jsx b/superset/assets/spec/javascripts/components/Checkbox_spec.jsx
index 8b74d12..5111766 100644
--- a/superset/assets/spec/javascripts/components/Checkbox_spec.jsx
+++ b/superset/assets/spec/javascripts/components/Checkbox_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import sinon from 'sinon';
 import { shallow } from 'enzyme';
 
@@ -20,19 +19,19 @@ describe('Checkbox', () => {
     wrapper = factory({});
   });
   it('is a valid element', () => {
-    expect(React.isValidElement(<Checkbox {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<Checkbox {...defaultProps} />)).toBe(true);
   });
   it('inits checked when checked', () => {
-    expect(wrapper.find('i.fa-check.text-primary')).to.have.length(1);
+    expect(wrapper.find('i.fa-check.text-primary')).toHaveLength(1);
   });
   it('inits unchecked when not checked', () => {
     const el = factory({ checked: false });
-    expect(el.find('i.fa-check.text-primary')).to.have.length(0);
-    expect(el.find('i.fa-check.text-transparent')).to.have.length(1);
+    expect(el.find('i.fa-check.text-primary')).toHaveLength(0);
+    expect(el.find('i.fa-check.text-transparent')).toHaveLength(1);
   });
   it('unchecks when clicked', () => {
-    expect(wrapper.find('i.fa-check.text-transparent')).to.have.length(0);
+    expect(wrapper.find('i.fa-check.text-transparent')).toHaveLength(0);
     wrapper.find('i').first().simulate('click');
-    expect(defaultProps.onChange.calledOnce).to.equal(true);
+    expect(defaultProps.onChange.calledOnce).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx b/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx
index 04529e1..3022a3a 100644
--- a/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx
+++ b/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import ColumnOption from '../../../src/components/ColumnOption';
@@ -25,25 +24,25 @@ describe('ColumnOption', () => {
     props = Object.assign({}, defaultProps);
   });
   it('is a valid element', () => {
-    expect(React.isValidElement(<ColumnOption {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<ColumnOption {...defaultProps} />)).toBe(true);
   });
   it('shows a label with verbose_name', () => {
     const lbl = wrapper.find('.option-label');
-    expect(lbl).to.have.length(1);
-    expect(lbl.first().text()).to.equal('Foo');
+    expect(lbl).toHaveLength(1);
+    expect(lbl.first().text()).toBe('Foo');
   });
   it('shows 2 InfoTooltipWithTrigger', () => {
-    expect(wrapper.find(InfoTooltipWithTrigger)).to.have.length(2);
+    expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(2);
   });
   it('shows only 1 InfoTooltipWithTrigger when no descr', () => {
     props.column.description = null;
     wrapper = shallow(factory(props));
-    expect(wrapper.find(InfoTooltipWithTrigger)).to.have.length(1);
+    expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(1);
   });
   it('shows a label with column_name when no verbose_name', () => {
     props.column.verbose_name = null;
     wrapper = shallow(factory(props));
-    expect(wrapper.find('.option-label').first().text()).to.equal('foo');
+    expect(wrapper.find('.option-label').first().text()).toBe('foo');
   });
   it('shows a column type label when showType is true', () => {
     wrapper = shallow(factory({
@@ -54,13 +53,13 @@ describe('ColumnOption', () => {
         type: 'str',
       },
     }));
-    expect(wrapper.find(ColumnTypeLabel)).to.have.length(1);
+    expect(wrapper.find(ColumnTypeLabel)).toHaveLength(1);
   });
   it('column with expression has correct column label if showType is true', () => {
     props.showType = true;
     wrapper = shallow(factory(props));
-    expect(wrapper.find(ColumnTypeLabel)).to.have.length(1);
-    expect(wrapper.find(ColumnTypeLabel).props().type).to.equal('expression');
+    expect(wrapper.find(ColumnTypeLabel)).toHaveLength(1);
+    expect(wrapper.find(ColumnTypeLabel).props().type).toBe('expression');
   });
   it('shows no column type label when type is null', () => {
     wrapper = shallow(factory({
@@ -71,13 +70,13 @@ describe('ColumnOption', () => {
         type: null,
       },
     }));
-    expect(wrapper.find(ColumnTypeLabel)).to.have.length(0);
+    expect(wrapper.find(ColumnTypeLabel)).toHaveLength(0);
   });
   it('dttm column has correct column label if showType is true', () => {
     props.showType = true;
     props.column.is_dttm = true;
     wrapper = shallow(factory(props));
-    expect(wrapper.find(ColumnTypeLabel)).to.have.length(1);
-    expect(wrapper.find(ColumnTypeLabel).props().type).to.equal('time');
+    expect(wrapper.find(ColumnTypeLabel)).toHaveLength(1);
+    expect(wrapper.find(ColumnTypeLabel).props().type).toBe('time');
   });
 });
diff --git a/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx b/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx
index 587469f..b708572 100644
--- a/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx
+++ b/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import ColumnTypeLabel from '../../../src/components/ColumnTypeLabel';
@@ -17,35 +16,35 @@ describe('ColumnOption', () => {
   }
 
   it('is a valid element', () => {
-    expect(React.isValidElement(<ColumnTypeLabel {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<ColumnTypeLabel {...defaultProps} />)).toBe(true);
   });
   it('string type shows ABC icon', () => {
     const lbl = getWrapper({}).find('.type-label');
-    expect(lbl).to.have.length(1);
-    expect(lbl.first().text()).to.equal('ABC');
+    expect(lbl).toHaveLength(1);
+    expect(lbl.first().text()).toBe('ABC');
   });
   it('int type shows # icon', () => {
     const lbl = getWrapper({ type: 'int(164)' }).find('.type-label');
-    expect(lbl).to.have.length(1);
-    expect(lbl.first().text()).to.equal('#');
+    expect(lbl).toHaveLength(1);
+    expect(lbl.first().text()).toBe('#');
   });
   it('bool type shows T/F icon', () => {
     const lbl = getWrapper({ type: 'BOOL' }).find('.type-label');
-    expect(lbl).to.have.length(1);
-    expect(lbl.first().text()).to.equal('T/F');
+    expect(lbl).toHaveLength(1);
+    expect(lbl.first().text()).toBe('T/F');
   });
   it('expression type shows function icon', () => {
     const lbl = getWrapper({ type: 'expression' }).find('.type-label');
-    expect(lbl).to.have.length(1);
-    expect(lbl.first().text()).to.equal('ƒ');
+    expect(lbl).toHaveLength(1);
+    expect(lbl.first().text()).toBe('ƒ');
   });
   it('unknown type shows question mark', () => {
     const lbl = getWrapper({ type: 'unknown' }).find('.type-label');
-    expect(lbl).to.have.length(1);
-    expect(lbl.first().text()).to.equal('?');
+    expect(lbl).toHaveLength(1);
+    expect(lbl.first().text()).toBe('?');
   });
-  it('unknown type shows question mark', () => {
+  it('datetime type displays', () => {
     const lbl = getWrapper({ type: 'datetime' }).find('.fa-clock-o');
-    expect(lbl).to.have.length(1);
+    expect(lbl).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx b/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx
index f8a0426..0c9473d 100644
--- a/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx
+++ b/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 
 import CopyToClipboard from '../../../src/components/CopyToClipboard';
 
@@ -11,6 +10,6 @@ describe('CopyToClipboard', () => {
   it('renders', () => {
     expect(
       React.isValidElement(<CopyToClipboard {...defaultProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx b/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
index 5232d8a..592f730 100644
--- a/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
+++ b/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { mount } from 'enzyme';
 import FilterableTable from '../../../../src/components/FilterableTable/FilterableTable';
 
@@ -17,11 +16,11 @@ describe('FilterableTable', () => {
     wrapper = mount(<FilterableTable {...mockedProps} />);
   });
   it('is valid element', () => {
-    expect(React.isValidElement(<FilterableTable {...mockedProps} />)).to.equal(true);
+    expect(React.isValidElement(<FilterableTable {...mockedProps} />)).toBe(true);
   });
   it('renders a grid with 2 rows', () => {
-    expect(wrapper.find('.ReactVirtualized__Grid')).to.have.length(1);
-    expect(wrapper.find('.ReactVirtualized__Table__row')).to.have.length(2);
+    expect(wrapper.find('.ReactVirtualized__Grid')).toHaveLength(1);
+    expect(wrapper.find('.ReactVirtualized__Table__row')).toHaveLength(2);
   });
   it('filters on a string', () => {
     const props = {
@@ -29,7 +28,7 @@ describe('FilterableTable', () => {
       filterText: 'b1',
     };
     wrapper = mount(<FilterableTable {...props} />);
-    expect(wrapper.find('.ReactVirtualized__Table__row')).to.have.length(1);
+    expect(wrapper.find('.ReactVirtualized__Table__row')).toHaveLength(1);
   });
   it('filters on a number', () => {
     const props = {
@@ -37,6 +36,6 @@ describe('FilterableTable', () => {
       filterText: '100',
     };
     wrapper = mount(<FilterableTable {...props} />);
-    expect(wrapper.find('.ReactVirtualized__Table__row')).to.have.length(1);
+    expect(wrapper.find('.ReactVirtualized__Table__row')).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/MetricOption_spec.jsx b/superset/assets/spec/javascripts/components/MetricOption_spec.jsx
index 3fd9230..02b2252 100644
--- a/superset/assets/spec/javascripts/components/MetricOption_spec.jsx
+++ b/superset/assets/spec/javascripts/components/MetricOption_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import MetricOption from '../../../src/components/MetricOption';
@@ -26,43 +25,43 @@ describe('MetricOption', () => {
     props = Object.assign({}, defaultProps);
   });
   it('is a valid element', () => {
-    expect(React.isValidElement(<MetricOption {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<MetricOption {...defaultProps} />)).toBe(true);
   });
   it('shows a label with verbose_name', () => {
     const lbl = wrapper.find('.option-label');
-    expect(lbl).to.have.length(1);
-    expect(lbl.first().text()).to.equal('Foo');
+    expect(lbl).toHaveLength(1);
+    expect(lbl.first().text()).toBe('Foo');
   });
   it('shows 3 InfoTooltipWithTrigger', () => {
-    expect(wrapper.find(InfoTooltipWithTrigger)).to.have.length(3);
+    expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(3);
   });
   it('shows only 2 InfoTooltipWithTrigger when no descr', () => {
     props.metric.description = null;
     wrapper = shallow(factory(props));
-    expect(wrapper.find(InfoTooltipWithTrigger)).to.have.length(2);
+    expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(2);
   });
   it('shows a label with metric_name when no verbose_name', () => {
     props.metric.verbose_name = null;
     wrapper = shallow(factory(props));
-    expect(wrapper.find('.option-label').first().text()).to.equal('foo');
+    expect(wrapper.find('.option-label').first().text()).toBe('foo');
   });
   it('shows only 1 InfoTooltipWithTrigger when no descr and no warning', () => {
     props.metric.warning_text = null;
     wrapper = shallow(factory(props));
-    expect(wrapper.find(InfoTooltipWithTrigger)).to.have.length(1);
+    expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(1);
   });
   it('sets target="_blank" when openInNewWindow is true', () => {
     props.url = 'https://github.com/apache/incubator-superset';
     wrapper = shallow(factory(props));
-    expect(wrapper.find('a').prop('target')).to.equal(null);
+    expect(wrapper.find('a').prop('target')).toBeNull();
 
     props.openInNewWindow = true;
     wrapper = shallow(factory(props));
-    expect(wrapper.find('a').prop('target')).to.equal('_blank');
+    expect(wrapper.find('a').prop('target')).toBe('_blank');
   });
   it('shows a metric type label when showType is true', () => {
     props.showType = true;
     wrapper = shallow(factory(props));
-    expect(wrapper.find(ColumnTypeLabel)).to.have.length(1);
+    expect(wrapper.find(ColumnTypeLabel)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx b/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx
index 41adf12..a704835 100644
--- a/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx
+++ b/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 
 import ModalTrigger from '../../../src/components/ModalTrigger';
 
@@ -13,6 +12,6 @@ describe('ModalTrigger', () => {
   it('is a valid element', () => {
     expect(
       React.isValidElement(<ModalTrigger {...defaultProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx b/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx
index 43b5a26..12ca368 100644
--- a/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx
+++ b/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import VirtualizedSelect from 'react-virtualized-select';
 import Select, { Creatable } from 'react-select';
@@ -44,25 +43,25 @@ describe('OnPasteSelect', () => {
 
   it('renders the supplied selectWrap component', () => {
     const select = wrapper.find(Select);
-    expect(select).to.have.lengthOf(1);
+    expect(select).toHaveLength(1);
   });
 
   it('renders custom selectWrap components', () => {
     props.selectWrap = Creatable;
     wrapper = shallow(<OnPasteSelect {...props} />);
-    expect(wrapper.find(Creatable)).to.have.lengthOf(1);
+    expect(wrapper.find(Creatable)).toHaveLength(1);
     props.selectWrap = VirtualizedSelect;
     wrapper = shallow(<OnPasteSelect {...props} />);
-    expect(wrapper.find(VirtualizedSelect)).to.have.lengthOf(1);
+    expect(wrapper.find(VirtualizedSelect)).toHaveLength(1);
   });
 
   describe('onPaste', () => {
     it('calls onChange with pasted values', () => {
       wrapper.instance().onPaste(evt);
       expected = props.options.slice(0, 4);
-      expect(props.onChange.calledWith(expected)).to.be.true;
-      expect(evt.preventDefault.called).to.be.true;
-      expect(props.isValidNewOption.callCount).to.equal(5);
+      expect(props.onChange.calledWith(expected)).toBe(true);
+      expect(evt.preventDefault.called).toBe(true);
+      expect(props.isValidNewOption.callCount).toBe(5);
     });
 
     it('calls onChange without any duplicate values and adds new values', () => {
@@ -75,10 +74,10 @@ describe('OnPasteSelect', () => {
         { label: 'Chi na', value: 'Chi na' },
       ];
       wrapper.instance().onPaste(evt);
-      expect(props.onChange.calledWith(expected)).to.be.true;
-      expect(evt.preventDefault.called).to.be.true;
-      expect(props.isValidNewOption.callCount).to.equal(9);
-      expect(props.options[0].value).to.equal(expected[2].value);
+      expect(props.onChange.calledWith(expected)).toBe(true);
+      expect(evt.preventDefault.called).toBe(true);
+      expect(props.isValidNewOption.callCount).toBe(9);
+      expect(props.options[0].value).toBe(expected[2].value);
       props.options.splice(0, 1);
     });
 
@@ -96,9 +95,9 @@ describe('OnPasteSelect', () => {
         props.options[2],
       ];
       wrapper.instance().onPaste(evt);
-      expect(props.onChange.calledWith(expected)).to.be.true;
-      expect(evt.preventDefault.called).to.be.true;
-      expect(props.isValidNewOption.callCount).to.equal(11);
+      expect(props.onChange.calledWith(expected)).toBe(true);
+      expect(evt.preventDefault.called).toBe(true);
+      expect(props.isValidNewOption.callCount).toBe(11);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx b/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx
index 4b818e1..ffe642d 100644
--- a/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx
+++ b/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import InfoTooltipWithTrigger from '../../../src/components/InfoTooltipWithTrigger';
 import OptionDescription from '../../../src/components/OptionDescription';
@@ -22,10 +21,10 @@ describe('OptionDescription', () => {
   });
 
   it('renders an InfoTooltipWithTrigger', () => {
-    expect(wrapper.find(InfoTooltipWithTrigger)).to.have.lengthOf(1);
+    expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(1);
   });
 
   it('renders a span with the label', () => {
-    expect(wrapper.find('.option-label').text()).to.equal('Some option');
+    expect(wrapper.find('.option-label').text()).toBe('Some option');
   });
 });
diff --git a/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx b/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx
index 33826fe..8392d51 100644
--- a/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx
+++ b/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import PopoverSection from '../../../src/components/PopoverSection';
@@ -22,12 +21,12 @@ describe('PopoverSection', () => {
     wrapper = factory();
   });
   it('renders', () => {
-    expect(React.isValidElement(<PopoverSection {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<PopoverSection {...defaultProps} />)).toBe(true);
   });
   it('is show an icon when selected', () => {
-    expect(wrapper.find('.fa-check')).to.have.length(1);
+    expect(wrapper.find('.fa-check')).toHaveLength(1);
   });
   it('is show no icon when not selected', () => {
-    expect(factory({ isSelected: false }).find('.fa-check')).to.have.length(0);
+    expect(factory({ isSelected: false }).find('.fa-check')).toHaveLength(0);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx b/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx
index 67edd08..ce88488 100644
--- a/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx
+++ b/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import configureStore from 'redux-mock-store';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import { OverlayTrigger } from 'react-bootstrap';
@@ -21,6 +20,6 @@ describe('URLShortLinkButton', () => {
 
   it('renders OverlayTrigger', () => {
     const wrapper = setup();
-    expect(wrapper.find(OverlayTrigger)).have.length(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx b/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx
index 6311262..5687e8f 100644
--- a/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx
+++ b/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import configureStore from 'redux-mock-store';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import URLShortLinkModal from '../../../src/components/URLShortLinkModal';
@@ -21,6 +20,6 @@ describe('URLShortLinkModal', () => {
 
   it('renders ModalTrigger', () => {
     const wrapper = setup();
-    expect(wrapper.find(ModalTrigger)).have.length(1);
+    expect(wrapper.find(ModalTrigger)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx b/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx
index a854f7e..530ba82 100644
--- a/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx
+++ b/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx
@@ -2,7 +2,6 @@
 import React from 'react';
 import sinon from 'sinon';
 import PropTypes from 'prop-types';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import VirtualizedRendererWrap from '../../../src/components/VirtualizedRendererWrap';
@@ -38,38 +37,34 @@ describe('VirtualizedRendererWrap', () => {
 
   it('uses the provided renderer', () => {
     const option = wrapper.find(TestOption);
-    expect(option).to.have.lengthOf(1);
+    expect(option).toHaveLength(1);
   });
 
   it('renders nothing when no option is provided', () => {
     props.option = null;
     wrapper = shallow(<RendererWrap {...props} />);
     const option = wrapper.find(TestOption);
-    expect(option).to.have.lengthOf(0);
+    expect(option).toHaveLength(0);
   });
 
   it('renders unfocused, unselected options with the default class', () => {
     const optionDiv = wrapper.find('div');
-    expect(optionDiv).to.have.lengthOf(1);
-    expect(optionDiv.props().className).to.equal('VirtualizedSelectOption');
+    expect(optionDiv).toHaveLength(1);
+    expect(optionDiv.props().className).toBe('VirtualizedSelectOption');
   });
 
   it('renders focused option with the correct class', () => {
     props.option = props.focusedOption;
     wrapper = shallow(<RendererWrap {...props} />);
     const optionDiv = wrapper.find('div');
-    expect(optionDiv.props().className).to.equal(
-      'VirtualizedSelectOption VirtualizedSelectFocusedOption',
-    );
+    expect(optionDiv.props().className).toBe('VirtualizedSelectOption VirtualizedSelectFocusedOption');
   });
 
   it('renders disabled option with the correct class', () => {
     props.option.disabled = true;
     wrapper = shallow(<RendererWrap {...props} />);
     const optionDiv = wrapper.find('div');
-    expect(optionDiv.props().className).to.equal(
-      'VirtualizedSelectOption VirtualizedSelectDisabledOption',
-    );
+    expect(optionDiv.props().className).toBe('VirtualizedSelectOption VirtualizedSelectDisabledOption');
     props.option.disabled = false;
   });
 
@@ -77,29 +72,25 @@ describe('VirtualizedRendererWrap', () => {
     props.valueArray = [props.option, props.focusedOption];
     wrapper = shallow(<RendererWrap {...props} />);
     const optionDiv = wrapper.find('div');
-    expect(optionDiv.props().className).to.equal(
-      'VirtualizedSelectOption VirtualizedSelectSelectedOption',
-    );
+    expect(optionDiv.props().className).toBe('VirtualizedSelectOption VirtualizedSelectSelectedOption');
   });
 
   it('renders options with custom classes', () => {
     props.option.className = 'CustomClass';
     wrapper = shallow(<RendererWrap {...props} />);
     const optionDiv = wrapper.find('div');
-    expect(optionDiv.props().className).to.equal(
-      'VirtualizedSelectOption CustomClass',
-    );
+    expect(optionDiv.props().className).toBe('VirtualizedSelectOption CustomClass');
   });
 
   it('calls focusedOption on its own option onMouseEnter', () => {
     const optionDiv = wrapper.find('div');
     optionDiv.simulate('mouseEnter');
-    expect(props.focusOption.calledWith(props.option)).to.be.true;
+    expect(props.focusOption.calledWith(props.option)).toBe(true);
   });
 
   it('calls selectValue on its own option onClick', () => {
     const optionDiv = wrapper.find('div');
     optionDiv.simulate('click');
-    expect(props.selectValue.calledWith(props.option)).to.be.true;
+    expect(props.selectValue.calledWith(props.option)).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
index 5be1191..ee9e436 100644
--- a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
@@ -1,4 +1,3 @@
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import { ActionCreators as UndoActionCreators } from 'redux-undo';
@@ -67,8 +66,8 @@ describe('dashboardLayout actions', () => {
       const nextComponents = { 1: {} };
       const thunk = updateComponents(nextComponents);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0]).to.deep.equal({
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0]).toEqual({
         type: UPDATE_COMPONENTS,
         payload: { nextComponents },
       });
@@ -81,10 +80,8 @@ describe('dashboardLayout actions', () => {
       const nextComponents = { 1: {} };
       const thunk = updateComponents(nextComponents);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(2);
-      expect(dispatch.getCall(1).args[0]).to.deep.equal(
-        setUnsavedChanges(true),
-      );
+      expect(dispatch.callCount).toBe(2);
+      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
     });
   });
 
@@ -93,8 +90,8 @@ describe('dashboardLayout actions', () => {
       const { getState, dispatch } = setup();
       const thunk = deleteComponent('id', 'parentId');
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0]).to.deep.equal({
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0]).toEqual({
         type: DELETE_COMPONENT,
         payload: { id: 'id', parentId: 'parentId' },
       });
@@ -106,10 +103,8 @@ describe('dashboardLayout actions', () => {
       });
       const thunk = deleteComponent('id', 'parentId');
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(2);
-      expect(dispatch.getCall(1).args[0]).to.deep.equal(
-        setUnsavedChanges(true),
-      );
+      expect(dispatch.callCount).toBe(2);
+      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
     });
   });
 
@@ -122,7 +117,7 @@ describe('dashboardLayout actions', () => {
       const thunk2 = dispatch.getCall(0).args[0];
       thunk2(dispatch, getState);
 
-      expect(dispatch.getCall(1).args[0]).to.deep.equal({
+      expect(dispatch.getCall(1).args[0]).toEqual({
         type: UPDATE_COMPONENTS,
         payload: {
           nextComponents: {
@@ -133,7 +128,7 @@ describe('dashboardLayout actions', () => {
         },
       });
 
-      expect(dispatch.callCount).to.equal(2);
+      expect(dispatch.callCount).toBe(2);
     });
   });
 
@@ -143,8 +138,8 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = createTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0]).to.deep.equal({
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0]).toEqual({
         type: CREATE_TOP_LEVEL_TABS,
         payload: { dropResult },
       });
@@ -157,10 +152,8 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = createTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(2);
-      expect(dispatch.getCall(1).args[0]).to.deep.equal(
-        setUnsavedChanges(true),
-      );
+      expect(dispatch.callCount).toBe(2);
+      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
     });
   });
 
@@ -170,8 +163,8 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = deleteTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0]).to.deep.equal({
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0]).toEqual({
         type: DELETE_TOP_LEVEL_TABS,
         payload: {},
       });
@@ -184,10 +177,8 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = deleteTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).to.equal(2);
-      expect(dispatch.getCall(1).args[0]).to.deep.equal(
-        setUnsavedChanges(true),
-      );
+      expect(dispatch.callCount).toBe(2);
+      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
     });
   });
 
@@ -217,8 +208,8 @@ describe('dashboardLayout actions', () => {
       const thunk2 = dispatch.getCall(0).args[0];
       thunk2(dispatch, getState);
 
-      expect(dispatch.callCount).to.equal(2);
-      expect(dispatch.getCall(1).args[0]).to.deep.equal({
+      expect(dispatch.callCount).toBe(2);
+      expect(dispatch.getCall(1).args[0]).toEqual({
         type: UPDATE_COMPONENTS,
         payload: {
           nextComponents: {
@@ -234,7 +225,7 @@ describe('dashboardLayout actions', () => {
         },
       });
 
-      expect(dispatch.callCount).to.equal(2);
+      expect(dispatch.callCount).toBe(2);
     });
 
     it('should dispatch a setUnsavedChanges action if hasUnsavedChanges=false', () => {
@@ -248,7 +239,7 @@ describe('dashboardLayout actions', () => {
       const thunk2 = dispatch.getCall(0).args[0];
       thunk2(dispatch, getState);
 
-      expect(dispatch.callCount).to.equal(3);
+      expect(dispatch.callCount).toBe(3);
     });
   });
 
@@ -267,14 +258,14 @@ describe('dashboardLayout actions', () => {
       const createComponentThunk = dispatch.getCall(0).args[0];
       createComponentThunk(dispatch, getState);
 
-      expect(dispatch.getCall(1).args[0]).to.deep.equal({
+      expect(dispatch.getCall(1).args[0]).toEqual({
         type: CREATE_COMPONENT,
         payload: {
           dropResult,
         },
       });
 
-      expect(dispatch.callCount).to.equal(2);
+      expect(dispatch.callCount).toBe(2);
     });
 
     it('should move a component if the component is not new', () => {
@@ -296,14 +287,14 @@ describe('dashboardLayout actions', () => {
       const moveComponentThunk = dispatch.getCall(0).args[0];
       moveComponentThunk(dispatch, getState);
 
-      expect(dispatch.getCall(1).args[0]).to.deep.equal({
+      expect(dispatch.getCall(1).args[0]).toEqual({
         type: MOVE_COMPONENT,
         payload: {
           dropResult,
         },
       });
 
-      expect(dispatch.callCount).to.equal(2);
+      expect(dispatch.callCount).toBe(2);
     });
 
     it('should dispatch a toast if the drop overflows the destination', () => {
@@ -325,11 +316,9 @@ describe('dashboardLayout actions', () => {
 
       const thunk = handleComponentDrop(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.getCall(0).args[0].type).to.deep.equal(
-        addInfoToast('').type,
-      );
+      expect(dispatch.getCall(0).args[0].type).toEqual(addInfoToast('').type);
 
-      expect(dispatch.callCount).to.equal(1);
+      expect(dispatch.callCount).toBe(1);
     });
 
     it('should delete a parent Row or Tabs if the moved child was the only child', () => {
@@ -360,7 +349,7 @@ describe('dashboardLayout actions', () => {
       const deleteThunk = dispatch.getCall(1).args[0];
       deleteThunk(dispatch, getState);
 
-      expect(dispatch.getCall(2).args[0]).to.deep.equal({
+      expect(dispatch.getCall(2).args[0]).toEqual({
         type: DELETE_COMPONENT,
         payload: {
           id: 'tabsId',
@@ -369,7 +358,7 @@ describe('dashboardLayout actions', () => {
       });
 
       // move thunk, delete thunk, delete result actions
-      expect(dispatch.callCount).to.equal(3);
+      expect(dispatch.callCount).toBe(3);
     });
 
     it('should create top-level tabs if dropped on root', () => {
@@ -386,14 +375,14 @@ describe('dashboardLayout actions', () => {
       const thunk2 = dispatch.getCall(0).args[0];
       thunk2(dispatch, getState);
 
-      expect(dispatch.getCall(1).args[0]).to.deep.equal({
+      expect(dispatch.getCall(1).args[0]).toEqual({
         type: CREATE_TOP_LEVEL_TABS,
         payload: {
           dropResult,
         },
       });
 
-      expect(dispatch.callCount).to.equal(2);
+      expect(dispatch.callCount).toBe(2);
     });
   });
 
@@ -405,10 +394,8 @@ describe('dashboardLayout actions', () => {
       const thunk = undoLayoutAction();
       thunk(dispatch, getState);
 
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0]).to.deep.equal(
-        UndoActionCreators.undo(),
-      );
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0]).toEqual(UndoActionCreators.undo());
     });
 
     it('should dispatch a setUnsavedChanges(false) action history length is zero', () => {
@@ -418,10 +405,8 @@ describe('dashboardLayout actions', () => {
       const thunk = undoLayoutAction();
       thunk(dispatch, getState);
 
-      expect(dispatch.callCount).to.equal(2);
-      expect(dispatch.getCall(1).args[0]).to.deep.equal(
-        setUnsavedChanges(false),
-      );
+      expect(dispatch.callCount).toBe(2);
+      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(false));
     });
   });
 
@@ -431,10 +416,8 @@ describe('dashboardLayout actions', () => {
       const thunk = redoLayoutAction();
       thunk(dispatch, getState);
 
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0]).to.deep.equal(
-        UndoActionCreators.redo(),
-      );
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0]).toEqual(UndoActionCreators.redo());
     });
 
     it('should dispatch a setUnsavedChanges(true) action if hasUnsavedChanges=false', () => {
@@ -444,10 +427,8 @@ describe('dashboardLayout actions', () => {
       const thunk = redoLayoutAction();
       thunk(dispatch, getState);
 
-      expect(dispatch.callCount).to.equal(2);
-      expect(dispatch.getCall(1).args[0]).to.deep.equal(
-        setUnsavedChanges(true),
-      );
+      expect(dispatch.callCount).toBe(2);
+      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx
index 094e1ee..bf31344 100644
--- a/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import CodeModal from '../../../../src/dashboard/components/CodeModal';
 
@@ -9,10 +8,10 @@ describe('CodeModal', () => {
     triggerNode: <i className="fa fa-edit" />,
   };
   it('is valid', () => {
-    expect(React.isValidElement(<CodeModal {...mockedProps} />)).to.equal(true);
+    expect(React.isValidElement(<CodeModal {...mockedProps} />)).toBe(true);
   });
   it('renders the trigger node', () => {
     const wrapper = mount(<CodeModal {...mockedProps} />);
-    expect(wrapper.find('.fa-edit')).to.have.length(1);
+    expect(wrapper.find('.fa-edit')).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx
index d630fe9..2a75d6e 100644
--- a/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import CssEditor from '../../../../src/dashboard/components/CssEditor';
 
@@ -9,10 +8,10 @@ describe('CssEditor', () => {
     triggerNode: <i className="fa fa-edit" />,
   };
   it('is valid', () => {
-    expect(React.isValidElement(<CssEditor {...mockedProps} />)).to.equal(true);
+    expect(React.isValidElement(<CssEditor {...mockedProps} />)).toBe(true);
   });
   it('renders the trigger node', () => {
     const wrapper = mount(<CssEditor {...mockedProps} />);
-    expect(wrapper.find('.fa-edit')).to.have.length(1);
+    expect(wrapper.find('.fa-edit')).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx
index 7215e08..7fb6b38 100644
--- a/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx
@@ -1,7 +1,6 @@
 import { Provider } from 'react-redux';
 import React from 'react';
 import { shallow, mount } from 'enzyme';
-import { expect } from 'chai';
 
 import ParentSize from '@vx/responsive/build/components/ParentSize';
 import { Sticky, StickyContainer } from 'react-sticky';
@@ -47,21 +46,21 @@ describe('DashboardBuilder', () => {
   it('should render a StickyContainer with class "dashboard"', () => {
     const wrapper = setup();
     const stickyContainer = wrapper.find(StickyContainer);
-    expect(stickyContainer).to.have.length(1);
-    expect(stickyContainer.prop('className')).to.equal('dashboard');
+    expect(stickyContainer).toHaveLength(1);
+    expect(stickyContainer.prop('className')).toBe('dashboard');
   });
 
   it('should add the "dashboard--editing" class if editMode=true', () => {
     const wrapper = setup({ editMode: true });
     const stickyContainer = wrapper.find(StickyContainer);
-    expect(stickyContainer.prop('className')).to.equal(
+    expect(stickyContainer.prop('className')).toBe(
       'dashboard dashboard--editing',
     );
   });
 
   it('should render a DragDroppable DashboardHeader', () => {
     const wrapper = setup(null, true);
-    expect(wrapper.find(DashboardHeader)).to.have.length(1);
+    expect(wrapper.find(DashboardHeader)).toHaveLength(1);
   });
 
   it('should render a Sticky top-level Tabs if the dashboard has tabs', () => {
@@ -74,19 +73,19 @@ describe('DashboardBuilder', () => {
     const dashboardComponent = sticky.find(DashboardComponent);
 
     const tabChildren = layoutWithTabs.TABS_ID.children;
-    expect(sticky).to.have.length(1);
-    expect(dashboardComponent).to.have.length(1 + tabChildren.length); // tab + tabs
-    expect(dashboardComponent.at(0).prop('id')).to.equal('TABS_ID');
+    expect(sticky).toHaveLength(1);
+    expect(dashboardComponent).toHaveLength(1 + tabChildren.length); // tab + tabs
+    expect(dashboardComponent.at(0).prop('id')).toBe('TABS_ID');
     tabChildren.forEach((tabId, i) => {
-      expect(dashboardComponent.at(i + 1).prop('id')).to.equal(tabId);
+      expect(dashboardComponent.at(i + 1).prop('id')).toBe(tabId);
     });
   });
 
   it('should render a TabContainer and TabContent', () => {
     const wrapper = setup({ dashboardLayout: layoutWithTabs });
     const parentSize = wrapper.find(ParentSize).dive();
-    expect(parentSize.find(TabContainer)).to.have.length(1);
-    expect(parentSize.find(TabContent)).to.have.length(1);
+    expect(parentSize.find(TabContainer)).toHaveLength(1);
+    expect(parentSize.find(TabContent)).toHaveLength(1);
   });
 
   it('should set animation=true, mountOnEnter=true, and unmounOnExit=false on TabContainer for perf', () => {
@@ -96,9 +95,9 @@ describe('DashboardBuilder', () => {
       .dive()
       .find(TabContainer)
       .props();
-    expect(tabProps.animation).to.equal(true);
-    expect(tabProps.mountOnEnter).to.equal(true);
-    expect(tabProps.unmountOnExit).to.equal(false);
+    expect(tabProps.animation).toBe(true);
+    expect(tabProps.mountOnEnter).toBe(true);
+    expect(tabProps.unmountOnExit).toBe(false);
   });
 
   it('should render a TabPane and DashboardGrid for each Tab', () => {
@@ -106,16 +105,16 @@ describe('DashboardBuilder', () => {
     const parentSize = wrapper.find(ParentSize).dive();
 
     const expectedCount = layoutWithTabs.TABS_ID.children.length;
-    expect(parentSize.find(TabPane)).to.have.length(expectedCount);
-    expect(parentSize.find(DashboardGrid)).to.have.length(expectedCount);
+    expect(parentSize.find(TabPane)).toHaveLength(expectedCount);
+    expect(parentSize.find(DashboardGrid)).toHaveLength(expectedCount);
   });
 
   it('should render a BuilderComponentPane if editMode=showBuilderPane=true', () => {
     const wrapper = setup();
-    expect(wrapper.find(BuilderComponentPane)).to.have.length(0);
+    expect(wrapper.find(BuilderComponentPane)).toHaveLength(0);
 
     wrapper.setProps({ ...props, editMode: true, showBuilderPane: true });
-    expect(wrapper.find(BuilderComponentPane)).to.have.length(1);
+    expect(wrapper.find(BuilderComponentPane)).toHaveLength(1);
   });
 
   it('should change tabs if a top-level Tab is clicked', () => {
@@ -125,13 +124,13 @@ describe('DashboardBuilder', () => {
       mockStoreWithTabs,
     );
 
-    expect(wrapper.find(TabContainer).prop('activeKey')).to.equal(0);
+    expect(wrapper.find(TabContainer).prop('activeKey')).toBe(0);
 
     wrapper
       .find('.dashboard-component-tabs .nav-tabs a')
       .at(1)
       .simulate('click');
 
-    expect(wrapper.find(TabContainer).prop('activeKey')).to.equal(1);
+    expect(wrapper.find(TabContainer).prop('activeKey')).toBe(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx
index 83f9760..04e89b9 100644
--- a/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import DashboardComponent from '../../../../src/dashboard/containers/DashboardComponent';
@@ -31,39 +30,37 @@ describe('DashboardGrid', () => {
 
   it('should render a div with class "dashboard-grid"', () => {
     const wrapper = setup();
-    expect(wrapper.find('.dashboard-grid')).to.have.length(1);
+    expect(wrapper.find('.dashboard-grid')).toHaveLength(1);
   });
 
   it('should render one DashboardComponent for each gridComponent child', () => {
     const wrapper = setup({
       gridComponent: { ...props.gridComponent, children: ['a', 'b'] },
     });
-    expect(wrapper.find(DashboardComponent)).to.have.length(2);
+    expect(wrapper.find(DashboardComponent)).toHaveLength(2);
   });
 
   it('should render two empty DragDroppables in editMode to increase the drop target zone', () => {
     const viewMode = setup({ editMode: false });
     const editMode = setup({ editMode: true });
-    expect(viewMode.find(DragDroppable)).to.have.length(0);
-    expect(editMode.find(DragDroppable)).to.have.length(2);
+    expect(viewMode.find(DragDroppable)).toHaveLength(0);
+    expect(editMode.find(DragDroppable)).toHaveLength(2);
   });
 
   it('should render grid column guides when resizing', () => {
     const wrapper = setup({ editMode: true });
-    expect(wrapper.find('.grid-column-guide')).to.have.length(0);
+    expect(wrapper.find('.grid-column-guide')).toHaveLength(0);
 
     wrapper.setState({ isResizing: true });
 
-    expect(wrapper.find('.grid-column-guide')).to.have.length(
-      GRID_COLUMN_COUNT,
-    );
+    expect(wrapper.find('.grid-column-guide')).toHaveLength(GRID_COLUMN_COUNT);
   });
 
   it('should render a grid row guide when resizing', () => {
     const wrapper = setup();
-    expect(wrapper.find('.grid-row-guide')).to.have.length(0);
+    expect(wrapper.find('.grid-row-guide')).toHaveLength(0);
     wrapper.setState({ isResizing: true, rowGuideTop: 10 });
-    expect(wrapper.find('.grid-row-guide')).to.have.length(1);
+    expect(wrapper.find('.grid-row-guide')).toHaveLength(1);
   });
 
   it('should call resizeComponent when a child DashboardComponent calls resizeStop', () => {
@@ -73,8 +70,8 @@ describe('DashboardGrid', () => {
     const dashboardComponent = wrapper.find(DashboardComponent).first();
     dashboardComponent.prop('onResizeStop')(args);
 
-    expect(resizeComponent.callCount).to.equal(1);
-    expect(resizeComponent.getCall(0).args[0]).to.deep.equal({
+    expect(resizeComponent.callCount).toBe(1);
+    expect(resizeComponent.getCall(0).args[0]).toEqual({
       id: 'id',
       width: 1,
       height: 3,
diff --git a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
index 76ff388..eeba4f5 100644
--- a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import Dashboard from '../../../../src/dashboard/components/Dashboard';
@@ -44,7 +43,7 @@ describe('Dashboard', () => {
 
   it('should render a DashboardBuilder', () => {
     const wrapper = setup();
-    expect(wrapper.find(DashboardBuilder)).to.have.length(1);
+    expect(wrapper.find(DashboardBuilder)).toHaveLength(1);
   });
 
   describe('refreshExcept', () => {
@@ -69,7 +68,7 @@ describe('Dashboard', () => {
       const spy = sinon.spy(props.actions, 'runQuery');
       wrapper.instance().refreshExcept('1001');
       spy.restore();
-      expect(spy.callCount).to.equal(Object.keys(overrideCharts).length - 1);
+      expect(spy.callCount).toBe(Object.keys(overrideCharts).length - 1);
     });
 
     it('should not call runQuery for filter_immune_slices', () => {
@@ -88,7 +87,7 @@ describe('Dashboard', () => {
       const spy = sinon.spy(props.actions, 'runQuery');
       wrapper.instance().refreshExcept();
       spy.restore();
-      expect(spy.callCount).to.equal(0);
+      expect(spy.callCount).toBe(0);
     });
   });
 
@@ -106,7 +105,7 @@ describe('Dashboard', () => {
         layout: layoutWithExtraChart,
       });
       spy.restore();
-      expect(spy.callCount).to.equal(1);
+      expect(spy.callCount).toBe(1);
     });
 
     it('should call removeSliceFromDashboard if a slice is removed from the layout', () => {
@@ -120,7 +119,7 @@ describe('Dashboard', () => {
         layout: nextLayout,
       });
       spy.restore();
-      expect(spy.callCount).to.equal(1);
+      expect(spy.callCount).toBe(1);
     });
   });
 
@@ -145,7 +144,7 @@ describe('Dashboard', () => {
       });
       wrapper.instance().componentDidUpdate(prevProps);
       refreshExceptSpy.restore();
-      expect(refreshExceptSpy.callCount).to.equal(0);
+      expect(refreshExceptSpy.callCount).toBe(0);
     });
 
     it('should call refresh if a filter is added', () => {
@@ -161,7 +160,7 @@ describe('Dashboard', () => {
         },
       });
       refreshExceptSpy.restore();
-      expect(refreshExceptSpy.callCount).to.equal(1);
+      expect(refreshExceptSpy.callCount).toBe(1);
     });
 
     it('should call refresh if a filter is removed', () => {
@@ -174,7 +173,7 @@ describe('Dashboard', () => {
         },
       });
       refreshExceptSpy.restore();
-      expect(refreshExceptSpy.callCount).to.equal(1);
+      expect(refreshExceptSpy.callCount).toBe(1);
     });
 
     it('should call refresh if a filter is changed', () => {
@@ -190,7 +189,7 @@ describe('Dashboard', () => {
         },
       });
       refreshExceptSpy.restore();
-      expect(refreshExceptSpy.callCount).to.equal(1);
+      expect(refreshExceptSpy.callCount).toBe(1);
     });
 
     it('should not call refresh if filters change and refresh is false', () => {
@@ -207,7 +206,7 @@ describe('Dashboard', () => {
         },
       });
       refreshExceptSpy.restore();
-      expect(refreshExceptSpy.callCount).to.equal(0);
+      expect(refreshExceptSpy.callCount).toBe(0);
     });
 
     it('should not refresh filter_immune_slices', () => {
@@ -235,7 +234,7 @@ describe('Dashboard', () => {
       });
       wrapper.instance().componentDidUpdate(prevProps);
       refreshExceptSpy.restore();
-      expect(refreshExceptSpy.callCount).to.equal(0);
+      expect(refreshExceptSpy.callCount).toBe(0);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
index 199b3d2..11b87ed 100644
--- a/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { DropdownButton, MenuItem } from 'react-bootstrap';
 import RefreshIntervalModal from '../../../../src/dashboard/components/RefreshIntervalModal';
@@ -41,32 +40,32 @@ describe('HeaderActionsDropdown', () => {
 
     it('should render the DropdownButton', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(DropdownButton)).to.have.lengthOf(1);
+      expect(wrapper.find(DropdownButton)).toHaveLength(1);
     });
 
     it('should not render the SaveModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(SaveModal)).to.have.lengthOf(0);
+      expect(wrapper.find(SaveModal)).toHaveLength(0);
     });
 
     it('should render one MenuItem', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(MenuItem)).to.have.lengthOf(1);
+      expect(wrapper.find(MenuItem)).toHaveLength(1);
     });
 
     it('should render the RefreshIntervalModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(RefreshIntervalModal)).to.have.lengthOf(1);
+      expect(wrapper.find(RefreshIntervalModal)).toHaveLength(1);
     });
 
     it('should render the URLShortLinkModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(URLShortLinkModal)).to.have.lengthOf(1);
+      expect(wrapper.find(URLShortLinkModal)).toHaveLength(1);
     });
 
     it('should not render the CssEditor', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(CssEditor)).to.have.lengthOf(0);
+      expect(wrapper.find(CssEditor)).toHaveLength(0);
     });
   });
 
@@ -75,32 +74,32 @@ describe('HeaderActionsDropdown', () => {
 
     it('should render the DropdownButton', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(DropdownButton)).to.have.lengthOf(1);
+      expect(wrapper.find(DropdownButton)).toHaveLength(1);
     });
 
     it('should render the SaveModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(SaveModal)).to.have.lengthOf(1);
+      expect(wrapper.find(SaveModal)).toHaveLength(1);
     });
 
     it('should render two MenuItems', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(MenuItem)).to.have.lengthOf(2);
+      expect(wrapper.find(MenuItem)).toHaveLength(2);
     });
 
     it('should render the RefreshIntervalModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(RefreshIntervalModal)).to.have.lengthOf(1);
+      expect(wrapper.find(RefreshIntervalModal)).toHaveLength(1);
     });
 
     it('should render the URLShortLinkModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(URLShortLinkModal)).to.have.lengthOf(1);
+      expect(wrapper.find(URLShortLinkModal)).toHaveLength(1);
     });
 
     it('should not render the CssEditor', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(CssEditor)).to.have.lengthOf(0);
+      expect(wrapper.find(CssEditor)).toHaveLength(0);
     });
   });
 
@@ -109,32 +108,32 @@ describe('HeaderActionsDropdown', () => {
 
     it('should render the DropdownButton', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(DropdownButton)).to.have.lengthOf(1);
+      expect(wrapper.find(DropdownButton)).toHaveLength(1);
     });
 
     it('should render the SaveModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(SaveModal)).to.have.lengthOf(1);
+      expect(wrapper.find(SaveModal)).toHaveLength(1);
     });
 
     it('should render three MenuItems', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(MenuItem)).to.have.lengthOf(3);
+      expect(wrapper.find(MenuItem)).toHaveLength(3);
     });
 
     it('should render the RefreshIntervalModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(RefreshIntervalModal)).to.have.lengthOf(1);
+      expect(wrapper.find(RefreshIntervalModal)).toHaveLength(1);
     });
 
     it('should render the URLShortLinkModal', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(URLShortLinkModal)).to.have.lengthOf(1);
+      expect(wrapper.find(URLShortLinkModal)).toHaveLength(1);
     });
 
     it('should render the CssEditor', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(CssEditor)).to.have.lengthOf(1);
+      expect(wrapper.find(CssEditor)).toHaveLength(1);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx
index 28153bb..b296586 100644
--- a/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import Header from '../../../../src/dashboard/components/Header';
 import EditableTitle from '../../../../src/components/EditableTitle';
@@ -56,27 +55,27 @@ describe('Header', () => {
 
     it('should render the EditableTitle', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(EditableTitle)).to.have.lengthOf(1);
+      expect(wrapper.find(EditableTitle)).toHaveLength(1);
     });
 
     it('should render the FaveStar', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(FaveStar)).to.have.lengthOf(1);
+      expect(wrapper.find(FaveStar)).toHaveLength(1);
     });
 
     it('should render the HeaderActionsDropdown', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(HeaderActionsDropdown)).to.have.lengthOf(1);
+      expect(wrapper.find(HeaderActionsDropdown)).toHaveLength(1);
     });
 
     it('should render one Button', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(Button)).to.have.lengthOf(1);
+      expect(wrapper.find(Button)).toHaveLength(1);
     });
 
     it('should not set up undo/redo', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(UndoRedoKeylisteners)).to.have.lengthOf(0);
+      expect(wrapper.find(UndoRedoKeylisteners)).toHaveLength(0);
     });
   });
 
@@ -88,27 +87,27 @@ describe('Header', () => {
 
     it('should render the EditableTitle', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(EditableTitle)).to.have.lengthOf(1);
+      expect(wrapper.find(EditableTitle)).toHaveLength(1);
     });
 
     it('should render the FaveStar', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(FaveStar)).to.have.lengthOf(1);
+      expect(wrapper.find(FaveStar)).toHaveLength(1);
     });
 
     it('should render the HeaderActionsDropdown', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(HeaderActionsDropdown)).to.have.lengthOf(1);
+      expect(wrapper.find(HeaderActionsDropdown)).toHaveLength(1);
     });
 
     it('should render one Button', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(Button)).to.have.lengthOf(1);
+      expect(wrapper.find(Button)).toHaveLength(1);
     });
 
     it('should not set up undo/redo', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(UndoRedoKeylisteners)).to.have.lengthOf(0);
+      expect(wrapper.find(UndoRedoKeylisteners)).toHaveLength(0);
     });
   });
 
@@ -120,27 +119,27 @@ describe('Header', () => {
 
     it('should render the EditableTitle', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(EditableTitle)).to.have.lengthOf(1);
+      expect(wrapper.find(EditableTitle)).toHaveLength(1);
     });
 
     it('should render the FaveStar', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(FaveStar)).to.have.lengthOf(1);
+      expect(wrapper.find(FaveStar)).toHaveLength(1);
     });
 
     it('should render the HeaderActionsDropdown', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(HeaderActionsDropdown)).to.have.lengthOf(1);
+      expect(wrapper.find(HeaderActionsDropdown)).toHaveLength(1);
     });
 
     it('should render four Buttons', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(Button)).to.have.lengthOf(4);
+      expect(wrapper.find(Button)).toHaveLength(4);
     });
 
     it('should set up undo/redo', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(UndoRedoKeylisteners)).to.have.lengthOf(1);
+      expect(wrapper.find(UndoRedoKeylisteners)).toHaveLength(1);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx
index e43f114..63e1299 100644
--- a/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import Loading from '../../../../src/components/Loading';
 import MissingChart from '../../../../src/dashboard/components/MissingChart';
@@ -13,16 +12,16 @@ describe('MissingChart', () => {
 
   it('renders a .missing-chart-container', () => {
     const wrapper = setup();
-    expect(wrapper.find('.missing-chart-container')).to.have.length(1);
+    expect(wrapper.find('.missing-chart-container')).toHaveLength(1);
   });
 
   it('renders a .missing-chart-body', () => {
     const wrapper = setup();
-    expect(wrapper.find('.missing-chart-body')).to.have.length(1);
+    expect(wrapper.find('.missing-chart-body')).toHaveLength(1);
   });
 
   it('renders a Loading', () => {
     const wrapper = setup();
-    expect(wrapper.find(Loading)).to.have.length(1);
+    expect(wrapper.find(Loading)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
index 7df7575..13f98b7 100644
--- a/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import RefreshIntervalModal from '../../../../src/dashboard/components/RefreshIntervalModal';
 
@@ -11,10 +10,10 @@ describe('RefreshIntervalModal', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<RefreshIntervalModal {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders the trigger node', () => {
     const wrapper = mount(<RefreshIntervalModal {...mockedProps} />);
-    expect(wrapper.find('.fa-edit')).to.have.length(1);
+    expect(wrapper.find('.fa-edit')).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx
index 704eb52..2d48252 100644
--- a/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import sinon from 'sinon';
-import { expect } from 'chai';
 
 import { List } from 'react-virtualized';
 
@@ -40,7 +39,7 @@ describe('SliceAdder', () => {
           }
           return currentTimestamp < sortedTimestamps[index - 1];
         }),
-      ).to.equal(true);
+      ).toBe(true);
     });
 
     it('should sort by slice_name', () => {
@@ -50,20 +49,20 @@ describe('SliceAdder', () => {
       const expectedNames = Object.values(props.slices)
         .map(slice => slice.slice_name)
         .sort();
-      expect(sortedNames).to.deep.equal(expectedNames);
+      expect(sortedNames).toEqual(expectedNames);
     });
   });
 
   it('render List', () => {
     const wrapper = shallow(<SliceAdder {...props} />);
     wrapper.setState({ filteredSlices: Object.values(props.slices) });
-    expect(wrapper.find(List)).to.have.length(1);
+    expect(wrapper.find(List)).toHaveLength(1);
   });
 
   it('render error', () => {
     const wrapper = shallow(<SliceAdder {...errorProps} />);
     wrapper.setState({ filteredSlices: Object.values(props.slices) });
-    expect(wrapper.text()).to.have.string(errorProps.errorMessage);
+    expect(wrapper.text()).toContain(errorProps.errorMessage);
   });
 
   it('componentDidMount', () => {
@@ -73,8 +72,8 @@ describe('SliceAdder', () => {
     shallow(<SliceAdder {...props} />, {
       lifecycleExperimental: true,
     });
-    expect(SliceAdder.prototype.componentDidMount.calledOnce).to.equal(true);
-    expect(props.fetchAllSlices.calledOnce).to.equal(true);
+    expect(SliceAdder.prototype.componentDidMount.calledOnce).toBe(true);
+    expect(props.fetchAllSlices.calledOnce).toBe(true);
 
     SliceAdder.prototype.componentDidMount.restore();
     props.fetchAllSlices.restore();
@@ -96,12 +95,12 @@ describe('SliceAdder', () => {
         ...props,
         lastUpdated: new Date().getTime(),
       });
-      expect(wrapper.instance().setState.calledOnce).to.equal(true);
+      expect(wrapper.instance().setState.calledOnce).toBe(true);
 
       const stateKeys = Object.keys(
         wrapper.instance().setState.lastCall.args[0],
       );
-      expect(stateKeys).to.include('filteredSlices');
+      expect(stateKeys).toContain('filteredSlices');
     });
 
     it('select slices should update state', () => {
@@ -109,12 +108,12 @@ describe('SliceAdder', () => {
         ...props,
         selectedSliceIds: [127],
       });
-      expect(wrapper.instance().setState.calledOnce).to.equal(true);
+      expect(wrapper.instance().setState.calledOnce).toBe(true);
 
       const stateKeys = Object.keys(
         wrapper.instance().setState.lastCall.args[0],
       );
-      expect(stateKeys).to.include('selectedSliceIdsSet');
+      expect(stateKeys).toContain('selectedSliceIdsSet');
     });
   });
 
@@ -133,21 +132,21 @@ describe('SliceAdder', () => {
     it('searchUpdated', () => {
       const newSearchTerm = 'new search term';
       wrapper.instance().searchUpdated(newSearchTerm);
-      expect(spy.calledOnce).to.equal(true);
-      expect(spy.lastCall.args[0]).to.equal(newSearchTerm);
+      expect(spy.calledOnce).toBe(true);
+      expect(spy.lastCall.args[0]).toBe(newSearchTerm);
     });
 
     it('handleSelect', () => {
       const newSortBy = 1;
       wrapper.instance().handleSelect(newSortBy);
-      expect(spy.calledOnce).to.equal(true);
-      expect(spy.lastCall.args[1]).to.equal(newSortBy);
+      expect(spy.calledOnce).toBe(true);
+      expect(spy.lastCall.args[1]).toBe(newSortBy);
     });
 
     it('handleKeyPress', () => {
       wrapper.instance().handleKeyPress(mockEvent);
-      expect(spy.calledOnce).to.equal(true);
-      expect(spy.lastCall.args[0]).to.equal(mockEvent.target.value);
+      expect(spy.calledOnce).toBe(true);
+      expect(spy.lastCall.args[0]).toBe(mockEvent.target.value);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx
index b45e9d8..3c67bf9 100644
--- a/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow, mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import newComponentFactory from '../../../../../src/dashboard/util/newComponentFactory';
@@ -33,18 +32,18 @@ describe('DragDroppable', () => {
 
   it('should render a div with class dragdroppable', () => {
     const wrapper = setup();
-    expect(wrapper.find('.dragdroppable')).to.have.length(1);
+    expect(wrapper.find('.dragdroppable')).toHaveLength(1);
   });
 
   it('should add class dragdroppable--dragging when dragging', () => {
     const wrapper = setup({ isDragging: true });
-    expect(wrapper.find('.dragdroppable')).to.have.length(1);
+    expect(wrapper.find('.dragdroppable')).toHaveLength(1);
   });
 
   it('should call its child function', () => {
     const childrenSpy = sinon.spy();
     setup({ children: childrenSpy });
-    expect(childrenSpy.callCount).to.equal(1);
+    expect(childrenSpy.callCount).toBe(1);
   });
 
   it('should call its child function with "dragSourceRef" if editMode=true', () => {
@@ -53,8 +52,8 @@ describe('DragDroppable', () => {
     setup({ children, editMode: false, dragSourceRef });
     setup({ children, editMode: true, dragSourceRef });
 
-    expect(children.getCall(0).args[0].dragSourceRef).to.equal(undefined);
-    expect(children.getCall(1).args[0].dragSourceRef).to.equal(dragSourceRef);
+    expect(children.getCall(0).args[0].dragSourceRef).toBeUndefined();
+    expect(children.getCall(1).args[0].dragSourceRef).toBe(dragSourceRef);
   });
 
   it('should call its child function with "dropIndicatorProps" dependent on editMode, isDraggingOver, state.dropIndicator is set', () => {
@@ -63,9 +62,9 @@ describe('DragDroppable', () => {
     wrapper.setState({ dropIndicator: 'nonsense' });
     wrapper.setProps({ ...props, editMode: true, isDraggingOver: true });
 
-    expect(children.callCount).to.equal(3); // initial + setState + setProps
-    expect(children.getCall(0).args[0].dropIndicatorProps).to.equal(undefined);
-    expect(children.getCall(2).args[0].dropIndicatorProps).to.deep.equal({
+    expect(children.callCount).toBe(3); // initial + setState + setProps
+    expect(children.getCall(0).args[0].dropIndicatorProps).toBeUndefined();
+    expect(children.getCall(2).args[0].dropIndicatorProps).toEqual({
       className: 'drop-indicator',
     });
   });
@@ -75,15 +74,15 @@ describe('DragDroppable', () => {
     const droppableRef = sinon.spy();
 
     setup({ dragPreviewRef, droppableRef }, true);
-    expect(dragPreviewRef.callCount).to.equal(1);
-    expect(droppableRef.callCount).to.equal(1);
+    expect(dragPreviewRef.callCount).toBe(1);
+    expect(droppableRef.callCount).toBe(1);
   });
 
   it('should set this.mounted dependent on life cycle', () => {
     const wrapper = setup({}, true);
     const instance = wrapper.instance();
-    expect(instance.mounted).to.equal(true);
+    expect(instance.mounted).toBe(true);
     wrapper.unmount();
-    expect(instance.mounted).to.equal(false);
+    expect(instance.mounted).toBe(false);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx
index a2e50e8..1d4ed19 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx
@@ -1,7 +1,6 @@
 import { Provider } from 'react-redux';
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import Chart from '../../../../../src/dashboard/containers/Chart';
@@ -50,22 +49,20 @@ describe('ChartHolder', () => {
 
   it('should render a DragDroppable', () => {
     const wrapper = setup();
-    expect(wrapper.find(DragDroppable)).to.have.length(1);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should render a ResizableContainer', () => {
     const wrapper = setup();
-    expect(wrapper.find(ResizableContainer)).to.have.length(1);
+    expect(wrapper.find(ResizableContainer)).toHaveLength(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,
-    );
+    expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).toBe(true);
 
     wrapper = setup({ ...props, parentComponent: mockLayout.present.CHART_ID });
-    expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).to.equal(
+    expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).toBe(
       false,
     );
   });
@@ -73,39 +70,39 @@ describe('ChartHolder', () => {
   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(
+    expect(resizableProps.widthStep).toBe(props.columnWidth);
+    expect(resizableProps.widthMultiple).toBe(props.component.meta.width);
+    expect(resizableProps.heightMultiple).toBe(props.component.meta.height);
+    expect(resizableProps.maxWidthMultiple).toBe(
       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);
+    expect(wrapper.find('.dashboard-component-chart-holder')).toHaveLength(1);
   });
 
   it('should render a Chart', () => {
     const wrapper = setup();
-    expect(wrapper.find(Chart)).to.have.length(1);
+    expect(wrapper.find(Chart)).toHaveLength(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);
+    expect(wrapper.find(HoverMenu)).toHaveLength(0);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(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);
+    expect(wrapper.find(HoverMenu)).toHaveLength(1);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(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);
+    expect(deleteComponent.callCount).toBe(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx
index db8b45a..e013e7b 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import Chart from '../../../../../src/dashboard/components/gridComponents/Chart';
@@ -50,39 +49,39 @@ describe('Chart', () => {
 
   it('should render a SliceHeader', () => {
     const wrapper = setup();
-    expect(wrapper.find(SliceHeader)).to.have.length(1);
+    expect(wrapper.find(SliceHeader)).toHaveLength(1);
   });
 
   it('should render a ChartContainer', () => {
     const wrapper = setup();
-    expect(wrapper.find(ChartContainer)).to.have.length(1);
+    expect(wrapper.find(ChartContainer)).toHaveLength(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);
+    expect(wrapper.find('.slice_description')).toHaveLength(0);
 
     wrapper.setProps({ ...props, isExpanded: true });
-    expect(wrapper.find('.slice_description')).to.have.length(1);
+    expect(wrapper.find('.slice_description')).toHaveLength(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);
+    expect(refreshChart.callCount).toBe(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);
+    expect(addFilter.callCount).toBe(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);
+    expect(wrapper.instance().getFilters()).toBe(filters);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx
index a0fbffd..852902e 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx
@@ -1,7 +1,6 @@
 import { Provider } from 'react-redux';
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import BackgroundStyleDropdown from '../../../../../src/dashboard/components/menu/BackgroundStyleDropdown';
@@ -59,42 +58,42 @@ describe('Column', () => {
   it('should render a DragDroppable', () => {
     // don't count child DragDroppables
     const wrapper = setup({ component: columnWithoutChildren });
-    expect(wrapper.find(DragDroppable)).to.have.length(1);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should render a WithPopoverMenu', () => {
     // don't count child DragDroppables
     const wrapper = setup({ component: columnWithoutChildren });
-    expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
+    expect(wrapper.find(WithPopoverMenu)).toHaveLength(1);
   });
 
   it('should render a ResizableContainer', () => {
     // don't count child DragDroppables
     const wrapper = setup({ component: columnWithoutChildren });
-    expect(wrapper.find(ResizableContainer)).to.have.length(1);
+    expect(wrapper.find(ResizableContainer)).toHaveLength(1);
   });
 
   it('should render a HoverMenu in editMode', () => {
     let wrapper = setup({ component: columnWithoutChildren });
-    expect(wrapper.find(HoverMenu)).to.have.length(0);
+    expect(wrapper.find(HoverMenu)).toHaveLength(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);
+    expect(wrapper.find(HoverMenu)).toHaveLength(1);
   });
 
   it('should render a DeleteComponentButton in editMode', () => {
     let wrapper = setup({ component: columnWithoutChildren });
-    expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(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);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(1);
   });
 
   it('should render a BackgroundStyleDropdown when focused', () => {
     let wrapper = setup({ component: columnWithoutChildren });
-    expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(0);
+    expect(wrapper.find(BackgroundStyleDropdown)).toHaveLength(0);
 
     // we cannot set props on the Row because of the WithDragDropContext wrapper
     wrapper = setup({ component: columnWithoutChildren, editMode: true });
@@ -103,20 +102,20 @@ describe('Column', () => {
       .at(1) // first one is delete button
       .simulate('click');
 
-    expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(1);
+    expect(wrapper.find(BackgroundStyleDropdown)).toHaveLength(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);
+    expect(deleteComponent.callCount).toBe(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(
+    expect(dashboardComponent.props().availableColumnCount).toBe(
       props.component.meta.width,
     );
   });
@@ -125,12 +124,12 @@ describe('Column', () => {
     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(
+    expect(resizableProps.adjustableWidth).toBe(true);
+    expect(resizableProps.adjustableHeight).toBe(false);
+    expect(resizableProps.widthStep).toBe(props.columnWidth);
+    expect(resizableProps.widthMultiple).toBe(columnWidth);
+    expect(resizableProps.minWidthMultiple).toBe(props.minColumnWidth);
+    expect(resizableProps.maxWidthMultiple).toBe(
       props.availableColumnCount + columnWidth,
     );
   });
@@ -138,6 +137,6 @@ describe('Column', () => {
   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);
+    expect(dashboardComponent.props().depth).toBe(props.depth + 1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx
index 0542532..7231937 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
@@ -41,29 +40,29 @@ describe('Divider', () => {
 
   it('should render a DragDroppable', () => {
     const wrapper = setup();
-    expect(wrapper.find(DragDroppable)).to.have.length(1);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should render a div with class "dashboard-component-divider"', () => {
     const wrapper = setup();
-    expect(wrapper.find('.dashboard-component-divider')).to.have.length(1);
+    expect(wrapper.find('.dashboard-component-divider')).toHaveLength(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);
+    expect(wrapper.find(HoverMenu)).toHaveLength(0);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(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);
+    expect(wrapper.find(HoverMenu)).toHaveLength(1);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(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);
+    expect(deleteComponent.callCount).toBe(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx
index f21f106..5b8b9e9 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
@@ -44,29 +43,27 @@ describe('Header', () => {
 
   it('should render a DragDroppable', () => {
     const wrapper = setup();
-    expect(wrapper.find(DragDroppable)).to.have.length(1);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should render a WithPopoverMenu', () => {
     const wrapper = setup();
-    expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
+    expect(wrapper.find(WithPopoverMenu)).toHaveLength(1);
   });
 
   it('should render a HoverMenu in editMode', () => {
     let wrapper = setup();
-    expect(wrapper.find(HoverMenu)).to.have.length(0);
+    expect(wrapper.find(HoverMenu)).toHaveLength(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);
+    expect(wrapper.find(HoverMenu)).toHaveLength(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,
-    );
+    expect(wrapper.find(EditableTitle)).toHaveLength(1);
+    expect(wrapper.find('input').prop('value')).toBe(props.component.meta.text);
   });
 
   it('should call updateComponents when EditableTitle changes', () => {
@@ -75,8 +72,8 @@ describe('Header', () => {
     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(
+    expect(updateComponents.callCount).toBe(1);
+    expect(updateComponents.getCall(0).args[0][headerId].meta.text).toBe(
       'New title',
     );
   });
@@ -85,7 +82,7 @@ describe('Header', () => {
     const wrapper = setup({ editMode: true });
     wrapper.find(WithPopoverMenu).simulate('click'); // focus
 
-    expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(1);
   });
 
   it('should call deleteComponent when deleted', () => {
@@ -94,6 +91,6 @@ describe('Header', () => {
     wrapper.find(WithPopoverMenu).simulate('click'); // focus
     wrapper.find(DeleteComponentButton).simulate('click');
 
-    expect(deleteComponent.callCount).to.equal(1);
+    expect(deleteComponent.callCount).toBe(1);
   });
 });
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 f3aceee..0382408 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx
@@ -1,7 +1,6 @@
 import { Provider } from 'react-redux';
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 import AceEditor from 'react-ace';
 import ReactMarkdown from 'react-markdown';
@@ -51,27 +50,25 @@ describe('Markdown', () => {
 
   it('should render a DragDroppable', () => {
     const wrapper = setup();
-    expect(wrapper.find(DragDroppable)).to.have.length(1);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should render a WithPopoverMenu', () => {
     const wrapper = setup();
-    expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
+    expect(wrapper.find(WithPopoverMenu)).toHaveLength(1);
   });
 
   it('should render a ResizableContainer', () => {
     const wrapper = setup();
-    expect(wrapper.find(ResizableContainer)).to.have.length(1);
+    expect(wrapper.find(ResizableContainer)).toHaveLength(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,
-    );
+    expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).toBe(true);
 
     wrapper = setup({ ...props, parentComponent: mockLayout.present.CHART_ID });
-    expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).to.equal(
+    expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).toBe(
       false,
     );
   });
@@ -79,34 +76,34 @@ describe('Markdown', () => {
   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(
+    expect(resizableProps.widthStep).toBe(props.columnWidth);
+    expect(resizableProps.widthMultiple).toBe(props.component.meta.width);
+    expect(resizableProps.heightMultiple).toBe(props.component.meta.height);
+    expect(resizableProps.maxWidthMultiple).toBe(
       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);
+    expect(wrapper.find(AceEditor)).toHaveLength(0);
+    expect(wrapper.find(ReactMarkdown)).toHaveLength(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);
+    expect(wrapper.find(AceEditor)).toHaveLength(0);
+    expect(wrapper.find(ReactMarkdown)).toHaveLength(1);
     wrapper.find(WithPopoverMenu).simulate('click'); // focus + edit
-    expect(wrapper.find(AceEditor)).to.have.length(1);
-    expect(wrapper.find(ReactMarkdown)).to.have.length(0);
+    expect(wrapper.find(AceEditor)).toHaveLength(1);
+    expect(wrapper.find(ReactMarkdown)).toHaveLength(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);
+    expect(wrapper.find(AceEditor)).toHaveLength(1);
+    expect(wrapper.find(ReactMarkdown)).toHaveLength(0);
 
     // we can't call setState on Markdown bc it's not the root component, so call
     // the mode dropdown onchange instead
@@ -114,8 +111,8 @@ describe('Markdown', () => {
     dropdown.prop('onChange')('preview');
     wrapper.update();
 
-    expect(wrapper.find(ReactMarkdown)).to.have.length(1);
-    expect(wrapper.find(AceEditor)).to.have.length(0);
+    expect(wrapper.find(ReactMarkdown)).toHaveLength(1);
+    expect(wrapper.find(AceEditor)).toHaveLength(0);
   });
 
   it('should call updateComponents when editMode changes from edit => preview, and there are markdownSource changes', () => {
@@ -127,7 +124,7 @@ describe('Markdown', () => {
     // the mode dropdown onchange instead
     const dropdown = wrapper.find(MarkdownModeDropdown);
     dropdown.prop('onChange')('preview');
-    expect(updateComponents.callCount).to.equal(0);
+    expect(updateComponents.callCount).toBe(0);
 
     dropdown.prop('onChange')('edit');
     // because we can't call setState on Markdown, change it through the editor
@@ -135,14 +132,14 @@ describe('Markdown', () => {
     const editor = wrapper.find(AceEditor);
     editor.prop('onChange')('new markdown!');
     dropdown.prop('onChange')('preview');
-    expect(updateComponents.callCount).to.equal(1);
+    expect(updateComponents.callCount).toBe(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);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(1);
   });
 
   it('should call deleteComponent when deleted', () => {
@@ -151,6 +148,6 @@ describe('Markdown', () => {
     wrapper.find(WithPopoverMenu).simulate('click'); // focus
     wrapper.find(DeleteComponentButton).simulate('click');
 
-    expect(deleteComponent.callCount).to.equal(1);
+    expect(deleteComponent.callCount).toBe(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx
index 54037c7..0bfaab7 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx
@@ -1,7 +1,6 @@
 import { Provider } from 'react-redux';
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import BackgroundStyleDropdown from '../../../../../src/dashboard/components/menu/BackgroundStyleDropdown';
@@ -55,36 +54,36 @@ describe('Row', () => {
   it('should render a DragDroppable', () => {
     // don't count child DragDroppables
     const wrapper = setup({ component: rowWithoutChildren });
-    expect(wrapper.find(DragDroppable)).to.have.length(1);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should render a WithPopoverMenu', () => {
     // don't count child DragDroppables
     const wrapper = setup({ component: rowWithoutChildren });
-    expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
+    expect(wrapper.find(WithPopoverMenu)).toHaveLength(1);
   });
 
   it('should render a HoverMenu in editMode', () => {
     let wrapper = setup({ component: rowWithoutChildren });
-    expect(wrapper.find(HoverMenu)).to.have.length(0);
+    expect(wrapper.find(HoverMenu)).toHaveLength(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);
+    expect(wrapper.find(HoverMenu)).toHaveLength(1);
   });
 
   it('should render a DeleteComponentButton in editMode', () => {
     let wrapper = setup({ component: rowWithoutChildren });
-    expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(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);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(1);
   });
 
   it('should render a BackgroundStyleDropdown when focused', () => {
     let wrapper = setup({ component: rowWithoutChildren });
-    expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(0);
+    expect(wrapper.find(BackgroundStyleDropdown)).toHaveLength(0);
 
     // we cannot set props on the Row because of the WithDragDropContext wrapper
     wrapper = setup({ component: rowWithoutChildren, editMode: true });
@@ -93,20 +92,20 @@ describe('Row', () => {
       .at(1) // first one is delete button
       .simulate('click');
 
-    expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(1);
+    expect(wrapper.find(BackgroundStyleDropdown)).toHaveLength(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);
+    expect(deleteComponent.callCount).toBe(1);
   });
 
   it('should pass appropriate availableColumnCount to children', () => {
     const wrapper = setup();
     const dashboardComponent = wrapper.find(DashboardComponent).first();
-    expect(dashboardComponent.props().availableColumnCount).to.equal(
+    expect(dashboardComponent.props().availableColumnCount).toBe(
       props.availableColumnCount - props.occupiedColumnCount,
     );
   });
@@ -114,6 +113,6 @@ describe('Row', () => {
   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);
+    expect(dashboardComponent.props().depth).toBe(props.depth + 1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx
index 1162e84..909f985 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx
@@ -1,7 +1,6 @@
 import { Provider } from 'react-redux';
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import DashboardComponent from '../../../../../src/dashboard/containers/DashboardComponent';
@@ -57,16 +56,14 @@ describe('Tabs', () => {
   describe('renderType=RENDER_TAB', () => {
     it('should render a DragDroppable', () => {
       const wrapper = setup();
-      expect(wrapper.find(DragDroppable)).to.have.length(1);
+      expect(wrapper.find(DragDroppable)).toHaveLength(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,
-      );
+      expect(title).toHaveLength(1);
+      expect(title.find('input').prop('value')).toBe(props.component.meta.text);
     });
 
     it('should call updateComponents when EditableTitle changes', () => {
@@ -74,25 +71,25 @@ describe('Tabs', () => {
       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(
+      expect(updateComponents.callCount).toBe(1);
+      expect(updateComponents.getCall(0).args[0].TAB_ID.meta.text).toBe(
         'New title',
       );
     });
 
     it('should render a WithPopoverMenu', () => {
       const wrapper = setup();
-      expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
+      expect(wrapper.find(WithPopoverMenu)).toHaveLength(1);
     });
 
     it('should render a DeleteComponentModal when focused if its not the only tab', () => {
       let wrapper = setup();
       wrapper.find(WithPopoverMenu).simulate('click'); // focus
-      expect(wrapper.find(DeleteComponentModal)).to.have.length(0);
+      expect(wrapper.find(DeleteComponentModal)).toHaveLength(0);
 
       wrapper = setup({ editMode: true });
       wrapper.find(WithPopoverMenu).simulate('click');
-      expect(wrapper.find(DeleteComponentModal)).to.have.length(1);
+      expect(wrapper.find(DeleteComponentModal)).toHaveLength(1);
 
       wrapper = setup({
         editMode: true,
@@ -102,7 +99,7 @@ describe('Tabs', () => {
         },
       });
       wrapper.find(WithPopoverMenu).simulate('click');
-      expect(wrapper.find(DeleteComponentModal)).to.have.length(0);
+      expect(wrapper.find(DeleteComponentModal)).toHaveLength(0);
     });
 
     it('should show modal when clicked delete icon', () => {
@@ -112,8 +109,8 @@ describe('Tabs', () => {
       wrapper.find('.icon-button').simulate('click');
 
       const modal = document.getElementsByClassName('modal');
-      expect(modal).to.have.length(1);
-      expect(deleteComponent.callCount).to.equal(0);
+      expect(modal).toHaveLength(1);
+      expect(deleteComponent.callCount).toBe(0);
     });
   });
 
@@ -121,7 +118,7 @@ describe('Tabs', () => {
     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);
+      expect(wrapper.find(DashboardComponent)).toHaveLength(2);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx
index 16f4360..d01437a 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx
@@ -1,7 +1,6 @@
 import { Provider } from 'react-redux';
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 import { Tabs as BootstrapTabs, Tab as BootstrapTab } from 'react-bootstrap';
 
@@ -53,32 +52,32 @@ describe('Tabs', () => {
   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);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should render BootstrapTabs', () => {
     const wrapper = setup();
-    expect(wrapper.find(BootstrapTabs)).to.have.length(1);
+    expect(wrapper.find(BootstrapTabs)).toHaveLength(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);
+    expect(tabProps.animation).toBe(true);
+    expect(tabProps.mountOnEnter).toBe(true);
+    expect(tabProps.unmountOnExit).toBe(false);
   });
 
   it('should render a BootstrapTab for each child', () => {
     const wrapper = setup();
-    expect(wrapper.find(BootstrapTab)).to.have.length(
+    expect(wrapper.find(BootstrapTab)).toHaveLength(
       props.component.children.length,
     );
   });
 
   it('should render an extra (+) BootstrapTab in editMode', () => {
     const wrapper = setup({ editMode: true });
-    expect(wrapper.find(BootstrapTab)).to.have.length(
+    expect(wrapper.find(BootstrapTab)).toHaveLength(
       props.component.children.length + 1,
     );
   });
@@ -86,7 +85,7 @@ describe('Tabs', () => {
   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(
+    expect(wrapper.find(DashboardComponent)).toHaveLength(
       props.component.children.length,
     );
   });
@@ -99,7 +98,7 @@ describe('Tabs', () => {
       .last()
       .simulate('click');
 
-    expect(createComponent.callCount).to.equal(1);
+    expect(createComponent.callCount).toBe(1);
   });
 
   it('should call onChangeTab when a tab is clicked', () => {
@@ -110,23 +109,23 @@ describe('Tabs', () => {
       .at(1) // will not call if it is already selected
       .simulate('click');
 
-    expect(onChangeTab.callCount).to.equal(1);
+    expect(onChangeTab.callCount).toBe(1);
   });
 
   it('should render a HoverMenu in editMode', () => {
     let wrapper = setup();
-    expect(wrapper.find(HoverMenu)).to.have.length(0);
+    expect(wrapper.find(HoverMenu)).toHaveLength(0);
 
     wrapper = setup({ editMode: true });
-    expect(wrapper.find(HoverMenu)).to.have.length(1);
+    expect(wrapper.find(HoverMenu)).toHaveLength(1);
   });
 
   it('should render a DeleteComponentButton in editMode', () => {
     let wrapper = setup();
-    expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(0);
 
     wrapper = setup({ editMode: true });
-    expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
+    expect(wrapper.find(DeleteComponentButton)).toHaveLength(1);
   });
 
   it('should call deleteComponent when deleted', () => {
@@ -134,6 +133,6 @@ describe('Tabs', () => {
     const wrapper = setup({ editMode: true, deleteComponent });
     wrapper.find(DeleteComponentButton).simulate('click');
 
-    expect(deleteComponent.callCount).to.equal(1);
+    expect(deleteComponent.callCount).toBe(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx
index 9e2993a..54a32c0 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import DragDroppable from '../../../../../../src/dashboard/components/dnd/DragDroppable';
 import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
@@ -33,13 +32,13 @@ describe('DraggableNewComponent', () => {
 
   it('should render a DragDroppable', () => {
     const wrapper = setup();
-    expect(wrapper.find(DragDroppable)).to.have.length(1);
+    expect(wrapper.find(DragDroppable)).toHaveLength(1);
   });
 
   it('should pass component={ type, id } to DragDroppable', () => {
     const wrapper = setup();
     const dragdroppable = wrapper.find(DragDroppable);
-    expect(dragdroppable.prop('component')).to.deep.equal({
+    expect(dragdroppable.prop('component')).toEqual({
       id: props.id,
       type: props.type,
     });
@@ -48,7 +47,7 @@ describe('DraggableNewComponent', () => {
   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({
+    expect(dragdroppable.prop('parentComponent')).toEqual({
       id: NEW_COMPONENTS_SOURCE_ID,
       type: NEW_COMPONENT_SOURCE_TYPE,
     });
@@ -56,12 +55,12 @@ describe('DraggableNewComponent', () => {
 
   it('should render the passed label', () => {
     const wrapper = setup();
-    expect(wrapper.find('.new-component').text()).to.equal(props.label);
+    expect(wrapper.find('.new-component').text()).toBe(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);
+    expect(wrapper.find(className)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx
index 240cf5e..0954d99 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
 import NewColumn from '../../../../../../src/dashboard/components/gridComponents/new/NewColumn';
@@ -15,12 +14,12 @@ describe('NewColumn', () => {
 
   it('should render a DraggableNewComponent', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
+    expect(wrapper.find(DraggableNewComponent)).toHaveLength(1);
   });
 
   it('should set appropriate type and id', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent).props()).to.include({
+    expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
       type: COLUMN_TYPE,
       id: NEW_COLUMN_ID,
     });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx
index af96f60..56adcb3 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
 import NewDivider from '../../../../../../src/dashboard/components/gridComponents/new/NewDivider';
@@ -15,12 +14,12 @@ describe('NewDivider', () => {
 
   it('should render a DraggableNewComponent', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
+    expect(wrapper.find(DraggableNewComponent)).toHaveLength(1);
   });
 
   it('should set appropriate type and id', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent).props()).to.include({
+    expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
       type: DIVIDER_TYPE,
       id: NEW_DIVIDER_ID,
     });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx
index 5f2194c..18cb602 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
 import NewHeader from '../../../../../../src/dashboard/components/gridComponents/new/NewHeader';
@@ -15,12 +14,12 @@ describe('NewHeader', () => {
 
   it('should render a DraggableNewComponent', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
+    expect(wrapper.find(DraggableNewComponent)).toHaveLength(1);
   });
 
   it('should set appropriate type and id', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent).props()).to.include({
+    expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
       type: HEADER_TYPE,
       id: NEW_HEADER_ID,
     });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx
index b86d167..e86067a 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
 import NewRow from '../../../../../../src/dashboard/components/gridComponents/new/NewRow';
@@ -15,12 +14,12 @@ describe('NewRow', () => {
 
   it('should render a DraggableNewComponent', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
+    expect(wrapper.find(DraggableNewComponent)).toHaveLength(1);
   });
 
   it('should set appropriate type and id', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent).props()).to.include({
+    expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
       type: ROW_TYPE,
       id: NEW_ROW_ID,
     });
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx
index edd13b7..ea77d69 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
 import NewTabs from '../../../../../../src/dashboard/components/gridComponents/new/NewTabs';
@@ -15,12 +14,12 @@ describe('NewTabs', () => {
 
   it('should render a DraggableNewComponent', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
+    expect(wrapper.find(DraggableNewComponent)).toHaveLength(1);
   });
 
   it('should set appropriate type and id', () => {
     const wrapper = setup();
-    expect(wrapper.find(DraggableNewComponent).props()).to.include({
+    expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
       type: TABS_TYPE,
       id: NEW_TABS_ID,
     });
diff --git a/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx
index 3d0fca7..727b14d 100644
--- a/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx
@@ -1,12 +1,11 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu';
 
 describe('HoverMenu', () => {
   it('should render a div.hover-menu', () => {
     const wrapper = shallow(<HoverMenu />);
-    expect(wrapper.find('.hover-menu')).to.have.length(1);
+    expect(wrapper.find('.hover-menu')).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx
index d382d25..84bb37d 100644
--- a/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu';
 
@@ -22,49 +21,49 @@ describe('WithPopoverMenu', () => {
 
   it('should render a div with class "with-popover-menu"', () => {
     const wrapper = setup();
-    expect(wrapper.find('.with-popover-menu')).to.have.length(1);
+    expect(wrapper.find('.with-popover-menu')).toHaveLength(1);
   });
 
   it('should render the passed children', () => {
     const wrapper = setup();
-    expect(wrapper.find('#child')).to.have.length(1);
+    expect(wrapper.find('#child')).toHaveLength(1);
   });
 
   it('should focus on click in editMode', () => {
     const wrapper = setup();
-    expect(wrapper.state('isFocused')).to.equal(false);
+    expect(wrapper.state('isFocused')).toBe(false);
 
     wrapper.simulate('click');
-    expect(wrapper.state('isFocused')).to.equal(false);
+    expect(wrapper.state('isFocused')).toBe(false);
 
     wrapper.setProps({ ...props, editMode: true });
     wrapper.simulate('click');
-    expect(wrapper.state('isFocused')).to.equal(true);
+    expect(wrapper.state('isFocused')).toBe(true);
   });
 
   it('should render menuItems when focused', () => {
     const wrapper = setup({ editMode: true });
-    expect(wrapper.find('#menu1')).to.have.length(0);
-    expect(wrapper.find('#menu2')).to.have.length(0);
+    expect(wrapper.find('#menu1')).toHaveLength(0);
+    expect(wrapper.find('#menu2')).toHaveLength(0);
 
     wrapper.simulate('click');
-    expect(wrapper.find('#menu1')).to.have.length(1);
-    expect(wrapper.find('#menu2')).to.have.length(1);
+    expect(wrapper.find('#menu1')).toHaveLength(1);
+    expect(wrapper.find('#menu2')).toHaveLength(1);
   });
 
   it('should not focus when disableClick=true', () => {
     const wrapper = setup({ disableClick: true, editMode: true });
-    expect(wrapper.state('isFocused')).to.equal(false);
+    expect(wrapper.state('isFocused')).toBe(false);
 
     wrapper.simulate('click');
-    expect(wrapper.state('isFocused')).to.equal(false);
+    expect(wrapper.state('isFocused')).toBe(false);
   });
 
   it('should use the passed shouldFocus func to determine if it should focus', () => {
     const wrapper = setup({ editMode: true, shouldFocus: () => false });
-    expect(wrapper.state('isFocused')).to.equal(false);
+    expect(wrapper.state('isFocused')).toBe(false);
 
     wrapper.simulate('click');
-    expect(wrapper.state('isFocused')).to.equal(false);
+    expect(wrapper.state('isFocused')).toBe(false);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx
index be4ae7c..e429dd1 100644
--- a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import Resizable from 're-resizable';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import ResizableContainer from '../../../../../src/dashboard/components/resizable/ResizableContainer';
 
@@ -14,6 +13,6 @@ describe('ResizableContainer', () => {
 
   it('should render a Resizable', () => {
     const wrapper = setup();
-    expect(wrapper.find(Resizable)).to.have.length(1);
+    expect(wrapper.find(Resizable)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx
index 66e4286..ae5ea7c 100644
--- a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx
@@ -1,20 +1,17 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import ResizableHandle from '../../../../../src/dashboard/components/resizable/ResizableHandle';
 
 describe('ResizableHandle', () => {
   it('should render a right resize handle', () => {
     const wrapper = shallow(<ResizableHandle.right />);
-    expect(wrapper.find('.resize-handle.resize-handle--right')).to.have.length(
-      1,
-    );
+    expect(wrapper.find('.resize-handle.resize-handle--right')).toHaveLength(1);
   });
 
   it('should render a bottom resize handle', () => {
     const wrapper = shallow(<ResizableHandle.bottom />);
-    expect(wrapper.find('.resize-handle.resize-handle--bottom')).to.have.length(
+    expect(wrapper.find('.resize-handle.resize-handle--bottom')).toHaveLength(
       1,
     );
   });
@@ -23,6 +20,6 @@ describe('ResizableHandle', () => {
     const wrapper = shallow(<ResizableHandle.bottomRight />);
     expect(
       wrapper.find('.resize-handle.resize-handle--bottom-right'),
-    ).to.have.length(1);
+    ).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/containers/Dashboard_spec.jsx b/superset/assets/spec/javascripts/dashboard/containers/Dashboard_spec.jsx
index 78d781b..13f069b 100644
--- a/superset/assets/spec/javascripts/dashboard/containers/Dashboard_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/containers/Dashboard_spec.jsx
@@ -2,7 +2,6 @@ import React from 'react';
 import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import Dashboard from '../../../../src/dashboard/containers/Dashboard';
 import getInitialState from '../../../../src/dashboard/reducers/getInitialState';
@@ -13,7 +12,7 @@ describe('Dashboard Container', () => {
   let store;
   let wrapper;
 
-  before(() => {
+  beforeAll(() => {
     const bootstrapData = {
       dashboard_data: {
         slices: [],
@@ -34,6 +33,6 @@ describe('Dashboard Container', () => {
   });
 
   it('should set feature flags', () => {
-    expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).to.equal(true);
+    expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js b/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js
index 1b805e0..09be3bd 100644
--- a/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import layoutReducer from '../../../../src/dashboard/reducers/dashboardLayout';
 
 import {
@@ -30,7 +28,7 @@ import {
 
 describe('dashboardLayout reducer', () => {
   it('should return initial state for unrecognized actions', () => {
-    expect(layoutReducer(undefined, {})).to.deep.equal({});
+    expect(layoutReducer(undefined, {})).toEqual({});
   });
 
   it('should delete a component, remove its reference in its parent, and recursively all of its children', () => {
@@ -60,7 +58,7 @@ describe('dashboardLayout reducer', () => {
           payload: { id: 'toDelete', parentId: 'parentId' },
         },
       ),
-    ).to.deep.equal({
+    ).toEqual({
       parentId: {
         id: 'parentId',
         children: ['anotherId'],
@@ -96,7 +94,7 @@ describe('dashboardLayout reducer', () => {
           payload: { id: 'toDelete', parentId: 'parentId' },
         },
       ),
-    ).to.deep.equal({
+    ).toEqual({
       grandparentId: {
         id: 'grandparentId',
         children: [],
@@ -138,7 +136,7 @@ describe('dashboardLayout reducer', () => {
           },
         },
       ),
-    ).to.deep.equal({
+    ).toEqual({
       update: {
         id: 'update',
         newField: 'newField',
@@ -185,7 +183,7 @@ describe('dashboardLayout reducer', () => {
         type: MOVE_COMPONENT,
         payload: { dropResult },
       }),
-    ).to.deep.equal({
+    ).toEqual({
       source: {
         id: 'source',
         type: ROW_TYPE,
@@ -239,9 +237,9 @@ describe('dashboardLayout reducer', () => {
         ['source', 'destination', 'toMove'].indexOf(component.id) === -1,
     );
 
-    expect(newRow.children[0]).to.equal('toMove');
-    expect(result.destination.children[0]).to.equal(newRow.id);
-    expect(Object.keys(result)).to.have.length(4);
+    expect(newRow.children[0]).toBe('toMove');
+    expect(result.destination.children[0]).toBe(newRow.id);
+    expect(Object.keys(result)).toHaveLength(4);
   });
 
   it('should add top-level tabs from a new tabs component, moving grid children to new tab', () => {
@@ -283,11 +281,11 @@ describe('dashboardLayout reducer', () => {
       component => component.type === TABS_TYPE,
     );
 
-    expect(Object.keys(result)).to.have.length(5); // initial + Tabs + Tab
-    expect(result[DASHBOARD_ROOT_ID].children[0]).to.equal(tabsComponent.id);
-    expect(result[tabsComponent.id].children[0]).to.equal(tabComponent.id);
-    expect(result[tabComponent.id].children[0]).to.equal('child');
-    expect(result[DASHBOARD_GRID_ID].children).to.have.length(0);
+    expect(Object.keys(result)).toHaveLength(5); // initial + Tabs + Tab
+    expect(result[DASHBOARD_ROOT_ID].children[0]).toBe(tabsComponent.id);
+    expect(result[tabsComponent.id].children[0]).toBe(tabComponent.id);
+    expect(result[tabComponent.id].children[0]).toBe('child');
+    expect(result[DASHBOARD_GRID_ID].children).toHaveLength(0);
   });
 
   it('should add top-level tabs from an existing tabs component, moving grid children to new tab', () => {
@@ -335,11 +333,11 @@ describe('dashboardLayout reducer', () => {
       payload: { dropResult },
     });
 
-    expect(Object.keys(result)).to.have.length(Object.keys(layout).length);
-    expect(result[DASHBOARD_ROOT_ID].children[0]).to.equal('tabs');
-    expect(result.tabs.children[0]).to.equal('tab');
-    expect(result.tab.children).to.deep.equal(['child', 'child2']);
-    expect(result[DASHBOARD_GRID_ID].children).to.have.length(0);
+    expect(Object.keys(result)).toHaveLength(Object.keys(layout).length);
+    expect(result[DASHBOARD_ROOT_ID].children[0]).toBe('tabs');
+    expect(result.tabs.children[0]).toBe('tab');
+    expect(result.tab.children).toEqual(['child', 'child2']);
+    expect(result[DASHBOARD_GRID_ID].children).toHaveLength(0);
   });
 
   it('should remove top-level tabs, moving children to the grid', () => {
@@ -387,7 +385,7 @@ describe('dashboardLayout reducer', () => {
       payload: { dropResult },
     });
 
-    expect(result).to.deep.equal({
+    expect(result).toEqual({
       [DASHBOARD_ROOT_ID]: {
         id: DASHBOARD_ROOT_ID,
         children: [DASHBOARD_GRID_ID],
@@ -436,7 +434,7 @@ describe('dashboardLayout reducer', () => {
     });
 
     const newId = result[DASHBOARD_GRID_ID].children[1];
-    expect(result[DASHBOARD_GRID_ID].children).to.have.length(2);
-    expect(result[newId].type).to.equal(ROW_TYPE);
+    expect(result[DASHBOARD_GRID_ID].children).toHaveLength(2);
+    expect(result[newId].type).toBe(ROW_TYPE);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js b/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js
index a8e3dbd..9ea1b17 100644
--- a/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import {
   ADD_SLICE,
   CHANGE_FILTER,
@@ -18,7 +16,7 @@ import dashboardStateReducer from '../../../../src/dashboard/reducers/dashboardS
 
 describe('dashboardState reducer', () => {
   it('should return initial state', () => {
-    expect(dashboardStateReducer(undefined, {})).to.deep.equal({});
+    expect(dashboardStateReducer(undefined, {})).toEqual({});
   });
 
   it('should add a slice', () => {
@@ -27,7 +25,7 @@ describe('dashboardState reducer', () => {
         { sliceIds: [1] },
         { type: ADD_SLICE, slice: { slice_id: 2 } },
       ),
-    ).to.deep.equal({ sliceIds: [1, 2] });
+    ).toEqual({ sliceIds: [1, 2] });
   });
 
   it('should remove a slice', () => {
@@ -36,7 +34,7 @@ describe('dashboardState reducer', () => {
         { sliceIds: [1, 2], filters: {} },
         { type: REMOVE_SLICE, sliceId: 2 },
       ),
-    ).to.deep.equal({ sliceIds: [1], refresh: false, filters: {} });
+    ).toEqual({ sliceIds: [1], refresh: false, filters: {} });
   });
 
   it('should reset filters if a removed slice is a filter', () => {
@@ -45,7 +43,7 @@ describe('dashboardState reducer', () => {
         { sliceIds: [1, 2], filters: { 2: {}, 1: {} } },
         { type: REMOVE_SLICE, sliceId: 2 },
       ),
-    ).to.deep.equal({ sliceIds: [1], filters: { 1: {} }, refresh: true });
+    ).toEqual({ sliceIds: [1], filters: { 1: {} }, refresh: true });
   });
 
   it('should toggle fav star', () => {
@@ -54,7 +52,7 @@ describe('dashboardState reducer', () => {
         { isStarred: false },
         { type: TOGGLE_FAVE_STAR, isStarred: true },
       ),
-    ).to.deep.equal({ isStarred: true });
+    ).toEqual({ isStarred: true });
   });
 
   it('should toggle edit mode', () => {
@@ -63,7 +61,7 @@ describe('dashboardState reducer', () => {
         { editMode: false },
         { type: SET_EDIT_MODE, editMode: true },
       ),
-    ).to.deep.equal({ editMode: true, showBuilderPane: true });
+    ).toEqual({ editMode: true, showBuilderPane: true });
   });
 
   it('should toggle builder pane', () => {
@@ -72,14 +70,14 @@ describe('dashboardState reducer', () => {
         { showBuilderPane: false },
         { type: TOGGLE_BUILDER_PANE },
       ),
-    ).to.deep.equal({ showBuilderPane: true });
+    ).toEqual({ showBuilderPane: true });
 
     expect(
       dashboardStateReducer(
         { showBuilderPane: true },
         { type: TOGGLE_BUILDER_PANE },
       ),
-    ).to.deep.equal({ showBuilderPane: false });
+    ).toEqual({ showBuilderPane: false });
   });
 
   it('should toggle expanded slices', () => {
@@ -88,18 +86,18 @@ describe('dashboardState reducer', () => {
         { expandedSlices: { 1: true, 2: false } },
         { type: TOGGLE_EXPAND_SLICE, sliceId: 1 },
       ),
-    ).to.deep.equal({ expandedSlices: { 2: false } });
+    ).toEqual({ expandedSlices: { 2: false } });
 
     expect(
       dashboardStateReducer(
         { expandedSlices: { 1: true, 2: false } },
         { type: TOGGLE_EXPAND_SLICE, sliceId: 2 },
       ),
-    ).to.deep.equal({ expandedSlices: { 1: true, 2: true } });
+    ).toEqual({ expandedSlices: { 1: true, 2: true } });
   });
 
   it('should set hasUnsavedChanges', () => {
-    expect(dashboardStateReducer({}, { type: ON_CHANGE })).to.deep.equal({
+    expect(dashboardStateReducer({}, { type: ON_CHANGE })).toEqual({
       hasUnsavedChanges: true,
     });
 
@@ -108,7 +106,7 @@ describe('dashboardState reducer', () => {
         {},
         { type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges: false } },
       ),
-    ).to.deep.equal({
+    ).toEqual({
       hasUnsavedChanges: false,
     });
   });
@@ -122,7 +120,7 @@ describe('dashboardState reducer', () => {
           payload: { maxUndoHistoryExceeded: true },
         },
       ),
-    ).to.deep.equal({
+    ).toEqual({
       maxUndoHistoryExceeded: true,
     });
   });
@@ -130,7 +128,7 @@ describe('dashboardState reducer', () => {
   it('should set unsaved changes, max undo history, and editMode to false on save', () => {
     expect(
       dashboardStateReducer({ hasUnsavedChanges: true }, { type: ON_SAVE }),
-    ).to.deep.equal({
+    ).toEqual({
       hasUnsavedChanges: false,
       maxUndoHistoryExceeded: false,
       editMode: false,
@@ -154,7 +152,7 @@ describe('dashboardState reducer', () => {
             merge: true,
           },
         ),
-      ).to.deep.equal({
+      ).toEqual({
         filters: { 1: { column: ['b', 'a'] } },
         refresh: true,
         sliceIds: [1],
@@ -179,7 +177,7 @@ describe('dashboardState reducer', () => {
             merge: false,
           },
         ),
-      ).to.deep.equal({
+      ).toEqual({
         filters: { 1: { column: ['b', 'a'] } },
         refresh: true,
         sliceIds: [1],
@@ -204,7 +202,7 @@ describe('dashboardState reducer', () => {
             merge: true,
           },
         ),
-      ).to.deep.equal({
+      ).toEqual({
         filters: { 1: { column: ['z', 'b', 'a'] } },
         refresh: true,
         sliceIds: [1],
@@ -229,7 +227,7 @@ describe('dashboardState reducer', () => {
             merge: false,
           },
         ),
-      ).to.deep.equal({
+      ).toEqual({
         filters: {},
         refresh: true,
         sliceIds: [1],
diff --git a/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js b/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js
index df43ae5..34c0e7a 100644
--- a/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import {
   FETCH_ALL_SLICES_FAILED,
   FETCH_ALL_SLICES_STARTED,
@@ -10,7 +8,7 @@ import sliceEntitiesReducer from '../../../../src/dashboard/reducers/sliceEntiti
 
 describe('sliceEntities reducer', () => {
   it('should return initial state', () => {
-    expect(sliceEntitiesReducer({}, {})).to.deep.equal({});
+    expect(sliceEntitiesReducer({}, {})).toEqual({});
   });
 
   it('should set loading when fetching slices', () => {
@@ -19,7 +17,7 @@ describe('sliceEntities reducer', () => {
         { isLoading: false },
         { type: FETCH_ALL_SLICES_STARTED },
       ).isLoading,
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('should set slices', () => {
@@ -28,12 +26,12 @@ describe('sliceEntities reducer', () => {
       { type: SET_ALL_SLICES, slices: { 1: {}, 2: {} } },
     );
 
-    expect(result.slices).to.deep.equal({
+    expect(result.slices).toEqual({
       1: {},
       2: {},
       a: {},
     });
-    expect(result.isLoading).to.equal(false);
+    expect(result.isLoading).toBe(false);
   });
 
   it('should set an error on error', () => {
@@ -44,7 +42,7 @@ describe('sliceEntities reducer', () => {
         error: { responseJSON: { message: 'errorrr' } },
       },
     );
-    expect(result.isLoading).to.equal(false);
-    expect(result.errorMessage.indexOf('errorrr')).to.be.above(-1);
+    expect(result.isLoading).toBe(false);
+    expect(result.errorMessage.indexOf('errorrr')).toBeGreaterThan(-1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js b/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js
index e8986be..543518e 100644
--- a/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import componentIsResizable from '../../../../src/dashboard/util/componentIsResizable';
 import {
   CHART_TYPE,
@@ -29,13 +27,13 @@ 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);
+      expect(componentIsResizable({ type })).toBe(true);
     });
   });
 
   notResizable.forEach(type => {
     it(`should return false for ${type}`, () => {
-      expect(componentIsResizable({ type })).to.equal(false);
+      expect(componentIsResizable({ type })).toBe(false);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js b/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js
index 7169229..0055535 100644
--- a/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import reorderItem from '../../../../src/dashboard/util/dnd-reorder';
 
 describe('dnd-reorderItem', () => {
@@ -19,8 +17,8 @@ describe('dnd-reorderItem', () => {
       destination: { id: 'b', index: 1 },
     });
 
-    expect(result.a.children).to.deep.equal(['x', 'y']);
-    expect(result.b.children).to.deep.equal(['banana', 'z']);
+    expect(result.a.children).toEqual(['x', 'y']);
+    expect(result.b.children).toEqual(['banana', 'z']);
   });
 
   it('should correctly move elements within the same list', () => {
@@ -35,7 +33,7 @@ describe('dnd-reorderItem', () => {
       destination: { id: 'a', index: 0 },
     });
 
-    expect(result.a.children).to.deep.equal(['z', 'x', 'y']);
+    expect(result.a.children).toEqual(['z', 'x', 'y']);
   });
 
   it('should copy items that do not move into the result', () => {
@@ -56,6 +54,6 @@ describe('dnd-reorderItem', () => {
       destination: { id: 'b', index: 1 },
     });
 
-    expect(result.iAmExtra === extraEntity).to.equal(true);
+    expect(result.iAmExtra === extraEntity).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js b/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js
index 3fc7d02..0ab5f5f 100644
--- a/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import dropOverflowsParent from '../../../../src/dashboard/util/dropOverflowsParent';
 import { NEW_COMPONENTS_SOURCE_ID } from '../../../../src/dashboard/util/constants';
 import {
@@ -40,7 +38,7 @@ describe('dropOverflowsParent', () => {
       },
     };
 
-    expect(dropOverflowsParent(dropResult, layout)).to.equal(true);
+    expect(dropOverflowsParent(dropResult, layout)).toBe(true);
   });
 
   it('returns false if a parent DOES have adequate width for child', () => {
@@ -72,7 +70,7 @@ describe('dropOverflowsParent', () => {
       },
     };
 
-    expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
+    expect(dropOverflowsParent(dropResult, layout)).toBe(false);
   });
 
   it('returns false if a child CAN shrink to available parent space', () => {
@@ -104,7 +102,7 @@ describe('dropOverflowsParent', () => {
       },
     };
 
-    expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
+    expect(dropOverflowsParent(dropResult, layout)).toBe(false);
   });
 
   it('returns true if a child CANNOT shrink to available parent space', () => {
@@ -137,7 +135,7 @@ describe('dropOverflowsParent', () => {
       },
     };
 
-    expect(dropOverflowsParent(dropResult, layout)).to.equal(true);
+    expect(dropOverflowsParent(dropResult, layout)).toBe(true);
   });
 
   it('returns true if a column has children that CANNOT shrink to available parent space', () => {
@@ -175,14 +173,14 @@ describe('dropOverflowsParent', () => {
       },
     };
 
-    expect(dropOverflowsParent(dropResult, layout)).to.equal(true);
+    expect(dropOverflowsParent(dropResult, layout)).toBe(true);
     // remove children
     expect(
       dropOverflowsParent(dropResult, {
         ...layout,
         dragging: { ...layout.dragging, children: [] },
       }),
-    ).to.equal(false);
+    ).toBe(false);
   });
 
   it('should work with new components that are not in the layout', () => {
@@ -200,7 +198,7 @@ describe('dropOverflowsParent', () => {
       },
     };
 
-    expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
+    expect(dropOverflowsParent(dropResult, layout)).toBe(false);
   });
 
   it('source/destination without widths should not overflow parent', () => {
@@ -221,6 +219,6 @@ describe('dropOverflowsParent', () => {
       },
     };
 
-    expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
+    expect(dropOverflowsParent(dropResult, layout)).toBe(false);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js b/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js
index 372f4cb..9e4a6a9 100644
--- a/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import findFirstParentContainerId from '../../../../src/dashboard/util/findFirstParentContainer';
 import {
   DASHBOARD_GRID_ID,
@@ -99,14 +97,12 @@ describe('findFirstParentContainer', () => {
   };
 
   it('should return grid root', () => {
-    expect(findFirstParentContainerId(mockGridLayout)).to.equal(
-      DASHBOARD_GRID_ID,
-    );
+    expect(findFirstParentContainerId(mockGridLayout)).toBe(DASHBOARD_GRID_ID);
   });
 
   it('should return first tab', () => {
     const tabsId = mockTabsLayout[DASHBOARD_ROOT_ID].children[0];
     const firstTabId = mockTabsLayout[tabsId].children[0];
-    expect(findFirstParentContainerId(mockTabsLayout)).to.equal(firstTabId);
+    expect(findFirstParentContainerId(mockTabsLayout)).toBe(firstTabId);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js b/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js
index 2ff15b2..111a447 100644
--- a/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import findParentId from '../../../../src/dashboard/util/findParentId';
 
 describe('findParentId', () => {
@@ -18,11 +16,11 @@ describe('findParentId', () => {
     },
   };
   it('should return the correct parentId', () => {
-    expect(findParentId({ childId: 'b', layout })).to.equal('a');
-    expect(findParentId({ childId: 'z', layout })).to.equal('b');
+    expect(findParentId({ childId: 'b', layout })).toBe('a');
+    expect(findParentId({ childId: 'z', layout })).toBe('b');
   });
 
   it('should return null if the parent cannot be found', () => {
-    expect(findParentId({ childId: 'a', layout })).to.equal(null);
+    expect(findParentId({ childId: 'a', layout })).toBeNull();
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js b/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js
index 3674ac5..29b99b4 100644
--- a/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import getChartIdsFromLayout from '../../../../src/dashboard/util/getChartIdsFromLayout';
 import {
   ROW_TYPE,
@@ -27,14 +25,14 @@ describe('getChartIdsFromLayout', () => {
 
   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);
+    expect(Array.isArray(result)).toBe(true);
+    expect(result.includes('A')).toBe(true);
+    expect(result.includes('B')).toBe(true);
   });
 
   it('should return ids only from CHART_TYPE components', () => {
     const result = getChartIdsFromLayout(mockLayout);
-    expect(result.length).to.equal(2);
-    expect(result.includes('C')).to.equal(false);
+    expect(result).toHaveLength(2);
+    expect(result.includes('C')).toBe(false);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
index cf58c78..75f12c2 100644
--- a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
@@ -1,12 +1,10 @@
-import { expect } from 'chai';
-
 import getDashboardUrl from '../../../../src/dashboard/util/getDashboardUrl';
 
 describe('getChartIdsFromLayout', () => {
   it('should encode filters', () => {
     const filters = { 35: { key: ['value'] } };
     const url = getDashboardUrl('path', filters);
-    expect(url).to.equal(
+    expect(url).toBe(
       'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
     );
   });
diff --git a/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js b/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js
index e977e28..eaa8c2d 100644
--- a/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import getDetailedComponentWidth from '../../../../src/dashboard/util/getDetailedComponentWidth';
 import * as types from '../../../../src/dashboard/util/componentTypes';
 import {
@@ -10,8 +8,10 @@ import {
 describe('getDetailedComponentWidth', () => {
   it('should return an object with width, minimumWidth, and occupiedWidth', () => {
     expect(
-      getDetailedComponentWidth({ id: '_', components: {} }),
-    ).to.have.all.keys(['minimumWidth', 'occupiedWidth', 'width']);
+      Object.keys(getDetailedComponentWidth({ id: '_', components: {} })),
+    ).toEqual(
+      expect.arrayContaining(['minimumWidth', 'occupiedWidth', 'width']),
+    );
   });
 
   describe('width', () => {
@@ -26,19 +26,19 @@ describe('getDetailedComponentWidth', () => {
         getDetailedComponentWidth({
           component: { id: '', type: types.HEADER_TYPE },
         }),
-      ).to.deep.equal(empty);
+      ).toEqual(empty);
 
       expect(
         getDetailedComponentWidth({
           component: { id: '', type: types.DIVIDER_TYPE },
         }),
-      ).to.deep.equal(empty);
+      ).toEqual(empty);
 
       expect(
         getDetailedComponentWidth({
           component: { id: '', type: types.TAB_TYPE },
         }),
-      ).to.deep.equal(empty);
+      ).toEqual(empty);
     });
 
     it('should match component meta width for resizeable components', () => {
@@ -46,20 +46,20 @@ describe('getDetailedComponentWidth', () => {
         getDetailedComponentWidth({
           component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
         }),
-      ).to.deep.equal({ width: 1, occupiedWidth: 1, minimumWidth: 1 });
+      ).toEqual({ 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 });
+      ).toEqual({ width: 2, occupiedWidth: 2, minimumWidth: 1 });
 
       expect(
         getDetailedComponentWidth({
           component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
         }),
         // note: occupiedWidth is zero for colunns/see test below
-      ).to.deep.equal({ width: 3, occupiedWidth: 0, minimumWidth: 1 });
+      ).toEqual({ width: 3, occupiedWidth: 0, minimumWidth: 1 });
     });
 
     it('should be GRID_COLUMN_COUNT for row components WITHOUT parents', () => {
@@ -68,7 +68,7 @@ describe('getDetailedComponentWidth', () => {
           id: 'row',
           components: { row: { id: 'row', type: types.ROW_TYPE } },
         }),
-      ).to.deep.equal({
+      ).toEqual({
         width: GRID_COLUMN_COUNT,
         occupiedWidth: 0,
         minimumWidth: GRID_MIN_COLUMN_COUNT,
@@ -89,7 +89,7 @@ describe('getDetailedComponentWidth', () => {
             },
           },
         }),
-      ).to.deep.equal({
+      ).toEqual({
         width: 7,
         occupiedWidth: 0,
         minimumWidth: GRID_MIN_COLUMN_COUNT,
@@ -104,13 +104,13 @@ describe('getDetailedComponentWidth', () => {
             id: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
           },
         }).width,
-      ).to.equal(6);
+      ).toBe(6);
 
       expect(
         getDetailedComponentWidth({
           component: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
         }).width,
-      ).to.equal(6);
+      ).toBe(6);
     });
   });
 
@@ -128,7 +128,7 @@ describe('getDetailedComponentWidth', () => {
             child: { id: 'child', meta: { width: 3.5 } },
           },
         }),
-      ).to.deep.equal({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
+      ).toEqual({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
     });
 
     it('should always be zero for column components', () => {
@@ -136,7 +136,7 @@ describe('getDetailedComponentWidth', () => {
         getDetailedComponentWidth({
           component: { id: '', type: types.COLUMN_TYPE, meta: { width: 2 } },
         }),
-      ).to.deep.equal({ width: 2, occupiedWidth: 0, minimumWidth: 1 });
+      ).toEqual({ width: 2, occupiedWidth: 0, minimumWidth: 1 });
     });
   });
 
@@ -146,7 +146,7 @@ describe('getDetailedComponentWidth', () => {
         getDetailedComponentWidth({
           component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
         }),
-      ).to.deep.equal({
+      ).toEqual({
         width: 1,
         minimumWidth: GRID_MIN_COLUMN_COUNT,
         occupiedWidth: 1,
@@ -156,7 +156,7 @@ describe('getDetailedComponentWidth', () => {
         getDetailedComponentWidth({
           component: { id: '', type: types.MARKDOWN_TYPE, meta: { width: 2 } },
         }),
-      ).to.deep.equal({
+      ).toEqual({
         width: 2,
         minimumWidth: GRID_MIN_COLUMN_COUNT,
         occupiedWidth: 2,
@@ -166,7 +166,7 @@ describe('getDetailedComponentWidth', () => {
         getDetailedComponentWidth({
           component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
         }),
-      ).to.deep.equal({
+      ).toEqual({
         width: 3,
         minimumWidth: GRID_MIN_COLUMN_COUNT,
         occupiedWidth: 0,
@@ -200,7 +200,7 @@ describe('getDetailedComponentWidth', () => {
           },
         }),
         // occupiedWidth is zero for colunns/see test below
-      ).to.deep.equal({ width: 12, occupiedWidth: 0, minimumWidth: 7 });
+      ).toEqual({ width: 12, occupiedWidth: 0, minimumWidth: 7 });
     });
 
     it('should equal occupiedWidth for row components', () => {
@@ -216,7 +216,7 @@ describe('getDetailedComponentWidth', () => {
             child: { id: 'child', meta: { width: 3.5 } },
           },
         }),
-      ).to.deep.equal({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
+      ).toEqual({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js b/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js
index 938a86a..43c6fb7 100644
--- a/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import getDropPosition, {
   DROP_TOP,
   DROP_RIGHT,
@@ -73,7 +71,7 @@ describe('getDropPosition', () => {
           draggingType: TAB_TYPE,
         }),
       );
-      expect(result).to.equal(null);
+      expect(result).toBeNull();
     });
   });
 
@@ -87,7 +85,7 @@ describe('getDropPosition', () => {
           draggingType: HEADER_TYPE,
         }),
       );
-      expect(result).to.equal(DROP_LEFT);
+      expect(result).toBe(DROP_LEFT);
     });
 
     it('should return DROP_RIGHT if component HAS children, and orientation is "row"', () => {
@@ -99,7 +97,7 @@ describe('getDropPosition', () => {
           hasChildren: true,
         }),
       );
-      expect(result).to.equal(DROP_RIGHT);
+      expect(result).toBe(DROP_RIGHT);
     });
 
     it('should return DROP_TOP if component has NO children, and orientation is "column"', () => {
@@ -111,7 +109,7 @@ describe('getDropPosition', () => {
           orientation: 'column',
         }),
       );
-      expect(result).to.equal(DROP_TOP);
+      expect(result).toBe(DROP_TOP);
     });
 
     it('should return DROP_BOTTOM if component HAS children, and orientation is "column"', () => {
@@ -124,7 +122,7 @@ describe('getDropPosition', () => {
           hasChildren: true,
         }),
       );
-      expect(result).to.equal(DROP_BOTTOM);
+      expect(result).toBe(DROP_BOTTOM);
     });
   });
 
@@ -143,7 +141,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_TOP);
+      expect(result).toBe(DROP_TOP);
     });
 
     it('should return DROP_BOTTOM if orientation="row" and clientOffset is closer to component bottom than top', () => {
@@ -159,7 +157,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_BOTTOM);
+      expect(result).toBe(DROP_BOTTOM);
     });
 
     it('should return DROP_LEFT if orientation="column" and clientOffset is closer to component left than right', () => {
@@ -176,7 +174,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_LEFT);
+      expect(result).toBe(DROP_LEFT);
     });
 
     it('should return DROP_RIGHT if orientation="column" and clientOffset is closer to component right than left', () => {
@@ -193,7 +191,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_RIGHT);
+      expect(result).toBe(DROP_RIGHT);
     });
   });
 
@@ -214,7 +212,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_LEFT);
+      expect(result).toBe(DROP_LEFT);
     });
 
     it('should return DROP_RIGHT if component HAS children, and clientOffset is NOT near top/bottom sibling boundary', () => {
@@ -233,7 +231,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_RIGHT);
+      expect(result).toBe(DROP_RIGHT);
     });
 
     it('should return DROP_TOP regardless of component children if clientOffset IS near top sibling boundary', () => {
@@ -266,8 +264,8 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(noChildren).to.equal(DROP_TOP);
-      expect(withChildren).to.equal(DROP_TOP);
+      expect(noChildren).toBe(DROP_TOP);
+      expect(withChildren).toBe(DROP_TOP);
     });
 
     it('should return DROP_BOTTOM regardless of component children if clientOffset IS near bottom sibling boundary', () => {
@@ -300,8 +298,8 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(noChildren).to.equal(DROP_BOTTOM);
-      expect(withChildren).to.equal(DROP_BOTTOM);
+      expect(noChildren).toBe(DROP_BOTTOM);
+      expect(withChildren).toBe(DROP_BOTTOM);
     });
   });
 
@@ -323,7 +321,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_TOP);
+      expect(result).toBe(DROP_TOP);
     });
 
     it('should return DROP_BOTTOM if component HAS children, and clientOffset is NOT near left/right sibling boundary', () => {
@@ -343,7 +341,7 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(result).to.equal(DROP_BOTTOM);
+      expect(result).toBe(DROP_BOTTOM);
     });
 
     it('should return DROP_LEFT regardless of component children if clientOffset IS near left sibling boundary', () => {
@@ -378,8 +376,8 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(noChildren).to.equal(DROP_LEFT);
-      expect(withChildren).to.equal(DROP_LEFT);
+      expect(noChildren).toBe(DROP_LEFT);
+      expect(withChildren).toBe(DROP_LEFT);
     });
 
     it('should return DROP_RIGHT regardless of component children if clientOffset IS near right sibling boundary', () => {
@@ -414,8 +412,8 @@ describe('getDropPosition', () => {
           },
         }),
       );
-      expect(noChildren).to.equal(DROP_RIGHT);
-      expect(withChildren).to.equal(DROP_RIGHT);
+      expect(noChildren).toBe(DROP_RIGHT);
+      expect(withChildren).toBe(DROP_RIGHT);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js b/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js
index 66ef5e7..3e1f7f5 100644
--- a/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import getFormDataWithExtraFilters from '../../../../src/dashboard/util/charts/getFormDataWithExtraFilters';
 
 describe('getFormDataWithExtraFilters', () => {
@@ -32,13 +30,13 @@ describe('getFormDataWithExtraFilters', () => {
 
   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({
+    expect(result.extra_filters).toHaveLength(2);
+    expect(result.extra_filters[0]).toEqual({
       col: 'region',
       op: 'in',
       val: ['Spain'],
     });
-    expect(result.extra_filters[1]).to.deep.equal({
+    expect(result.extra_filters[1]).toEqual({
       col: 'color',
       op: 'in',
       val: ['pink', 'purple'],
@@ -52,7 +50,7 @@ describe('getFormDataWithExtraFilters', () => {
         filter_immune_slices: [chartId],
       },
     });
-    expect(result.extra_filters).to.have.length(0);
+    expect(result.extra_filters).toHaveLength(0);
   });
 
   it('should not add additional filters for fields to which the slice is immune', () => {
@@ -64,6 +62,6 @@ describe('getFormDataWithExtraFilters', () => {
         },
       },
     });
-    expect(result.extra_filters).to.have.length(1);
+    expect(result.extra_filters).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js b/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js
index 2a0efcb..7f212fb 100644
--- a/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import isValidChild from '../../../../src/dashboard/util/isValidChild';
 
 import {
@@ -88,7 +86,7 @@ describe('isValidChild', () => {
                 parentType,
                 childType,
               }),
-            ).to.equal(true);
+            ).toBe(true);
           });
         }
         // see isValidChild.js for why tabs do not increment the depth of their children
@@ -135,7 +133,7 @@ describe('isValidChild', () => {
                 parentType,
                 childType,
               }),
-            ).to.equal(false);
+            ).toBe(false);
           });
         }
         // see isValidChild.js for why tabs do not increment the depth of their children
diff --git a/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js b/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js
index b7cdb70..d94889b 100644
--- a/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import newComponentFactory from '../../../../src/dashboard/util/newComponentFactory';
 
 import {
@@ -35,16 +33,16 @@ describe('newEntityFactory', () => {
     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);
+      expect(result.type).toBe(type);
+      expect(typeof result.id).toBe('string');
+      expect(typeof result.meta).toBe('object');
+      expect(Array.isArray(result.children)).toBe(true);
     });
   });
 
   it('adds passed meta data to the entity', () => {
     const banana = 'banana';
     const result = newComponentFactory(CHART_TYPE, { banana });
-    expect(result.meta.banana).to.equal(banana);
+    expect(result.meta.banana).toBe(banana);
   });
 });
diff --git a/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js b/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js
index c861827..9ad26e3 100644
--- a/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import newEntitiesFromDrop from '../../../../src/dashboard/util/newEntitiesFromDrop';
 import {
   CHART_TYPE,
@@ -27,9 +25,9 @@ describe('newEntitiesFromDrop', () => {
     });
 
     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);
+    expect(result.a.children).toHaveLength(1);
+    expect(Object.keys(result)).toHaveLength(2);
+    expect(result[newId].type).toBe(CHART_TYPE);
   });
 
   it('should create Tab AND Tabs components if the drag entity is Tabs', () => {
@@ -51,10 +49,10 @@ describe('newEntitiesFromDrop', () => {
     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);
+    expect(result.a.children).toHaveLength(1);
+    expect(Object.keys(result)).toHaveLength(3);
+    expect(result[newTabsId].type).toBe(TABS_TYPE);
+    expect(result[newTabId].type).toBe(TAB_TYPE);
   });
 
   it('should create a Row if the drag entity should be wrapped in a row', () => {
@@ -76,9 +74,9 @@ describe('newEntitiesFromDrop', () => {
     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);
+    expect(result.a.children).toHaveLength(1);
+    expect(Object.keys(result)).toHaveLength(3);
+    expect(result[newRowId].type).toBe(ROW_TYPE);
+    expect(result[newChartId].type).toBe(CHART_TYPE);
   });
 });
diff --git a/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx b/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx
index 0b7c6ab..7c8984e 100644
--- a/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx
+++ b/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { Tabs } from 'react-bootstrap';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import configureStore from 'redux-mock-store';
 import $ from 'jquery';
@@ -47,26 +46,26 @@ describe('DatasourceEditor', () => {
   });
 
   it('is valid', () => {
-    expect(React.isValidElement(el)).to.equal(true);
+    expect(React.isValidElement(el)).toBe(true);
   });
 
   it('renders Tabs', () => {
-    expect(wrapper.find(Tabs)).to.have.lengthOf(1);
+    expect(wrapper.find(Tabs)).toHaveLength(1);
   });
 
   it('makes an async request', () => {
     wrapper.setState({ activeTabKey: 2 });
     const syncButton = wrapper.find('.sync-from-source');
-    expect(syncButton).to.have.lengthOf(1);
+    expect(syncButton).toHaveLength(1);
     syncButton.simulate('click');
-    expect(ajaxStub.calledOnce).to.equal(true);
+    expect(ajaxStub.calledOnce).toBe(true);
   });
 
   it('merges columns', () => {
     const numCols = props.datasource.columns.length;
-    expect(inst.state.databaseColumns.length).to.equal(numCols);
+    expect(inst.state.databaseColumns).toHaveLength(numCols);
     inst.mergeColumns([extraColumn]);
-    expect(inst.state.databaseColumns.length).to.equal(numCols + 1);
+    expect(inst.state.databaseColumns).toHaveLength(numCols + 1);
   });
 
 });
diff --git a/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx b/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx
index ef0a4f4..7c9ed6d 100644
--- a/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx
+++ b/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { Modal } from 'react-bootstrap';
-import { expect } from 'chai';
 import configureStore from 'redux-mock-store';
 import { shallow } from 'enzyme';
 import $ from 'jquery';
@@ -40,19 +39,19 @@ describe('DatasourceModal', () => {
   });
 
   it('is valid', () => {
-    expect(React.isValidElement(el)).to.equal(true);
+    expect(React.isValidElement(el)).toBe(true);
   });
 
   it('renders a Modal', () => {
-    expect(wrapper.find(Modal)).to.have.lengthOf(1);
+    expect(wrapper.find(Modal)).toHaveLength(1);
   });
 
   it('renders a DatasourceEditor', () => {
-    expect(wrapper.find(DatasourceEditor)).to.have.lengthOf(1);
+    expect(wrapper.find(DatasourceEditor)).toHaveLength(1);
   });
 
   it('saves on confirm', () => {
     inst.onConfirmSave();
-    expect(ajaxStub.calledOnce).to.equal(true);
+    expect(ajaxStub.calledOnce).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js b/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js
index eb3161e..4ccd963 100644
--- a/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js
+++ b/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../../../src/explore/AdhocFilter';
 
 describe('AdhocFilter', () => {
@@ -11,8 +9,8 @@ describe('AdhocFilter', () => {
       comparator: '10',
       clause: CLAUSES.WHERE,
     });
-    expect(adhocFilter.filterOptionName.length).to.be.above(10);
-    expect(adhocFilter).to.deep.equal({
+    expect(adhocFilter.filterOptionName.length).toBeGreaterThan(10);
+    expect(adhocFilter).toEqual({
       expressionType: EXPRESSION_TYPES.SIMPLE,
       subject: 'value',
       operator: '>',
@@ -34,13 +32,13 @@ describe('AdhocFilter', () => {
     });
     const adhocFilter2 = adhocFilter1.duplicateWith({ operator: '<' });
 
-    expect(adhocFilter1.subject).to.equal(adhocFilter2.subject);
-    expect(adhocFilter1.comparator).to.equal(adhocFilter2.comparator);
-    expect(adhocFilter1.clause).to.equal(adhocFilter2.clause);
-    expect(adhocFilter1.expressionType).to.equal(adhocFilter2.expressionType);
+    expect(adhocFilter1.subject).toBe(adhocFilter2.subject);
+    expect(adhocFilter1.comparator).toBe(adhocFilter2.comparator);
+    expect(adhocFilter1.clause).toBe(adhocFilter2.clause);
+    expect(adhocFilter1.expressionType).toBe(adhocFilter2.expressionType);
 
-    expect(adhocFilter1.operator).to.equal('>');
-    expect(adhocFilter2.operator).to.equal('<');
+    expect(adhocFilter1.operator).toBe('>');
+    expect(adhocFilter2.operator).toBe('<');
   });
 
   it('can verify equality', () => {
@@ -54,9 +52,9 @@ describe('AdhocFilter', () => {
     const adhocFilter2 = adhocFilter1.duplicateWith({});
 
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter1.equals(adhocFilter2)).to.be.true;
+    expect(adhocFilter1.equals(adhocFilter2)).toBe(true);
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter1 === adhocFilter2).to.be.false;
+    expect(adhocFilter1 === adhocFilter2).toBe(false);
   });
 
   it('can verify inequality', () => {
@@ -70,7 +68,7 @@ describe('AdhocFilter', () => {
     const adhocFilter2 = adhocFilter1.duplicateWith({ operator: '<' });
 
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter1.equals(adhocFilter2)).to.be.false;
+    expect(adhocFilter1.equals(adhocFilter2)).toBe(false);
 
     const adhocFilter3 = new AdhocFilter({
       expressionType: EXPRESSION_TYPES.SQL,
@@ -80,7 +78,7 @@ describe('AdhocFilter', () => {
     const adhocFilter4 = adhocFilter3.duplicateWith({ sqlExpression: 'value = 5' });
 
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter3.equals(adhocFilter4)).to.be.false;
+    expect(adhocFilter3.equals(adhocFilter4)).toBe(false);
   });
 
   it('can determine if it is valid', () => {
@@ -92,7 +90,7 @@ describe('AdhocFilter', () => {
       clause: CLAUSES.WHERE,
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter1.isValid()).to.be.true;
+    expect(adhocFilter1.isValid()).toBe(true);
 
     const adhocFilter2 = new AdhocFilter({
       expressionType: EXPRESSION_TYPES.SIMPLE,
@@ -102,7 +100,7 @@ describe('AdhocFilter', () => {
       clause: CLAUSES.WHERE,
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter2.isValid()).to.be.false;
+    expect(adhocFilter2.isValid()).toBe(false);
 
     const adhocFilter3 = new AdhocFilter({
       expressionType: EXPRESSION_TYPES.SQL,
@@ -110,7 +108,7 @@ describe('AdhocFilter', () => {
       clause: null,
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter3.isValid()).to.be.false;
+    expect(adhocFilter3.isValid()).toBe(false);
 
     const adhocFilter4 = new AdhocFilter({
       expressionType: EXPRESSION_TYPES.SIMPLE,
@@ -120,7 +118,7 @@ describe('AdhocFilter', () => {
       clause: CLAUSES.WHERE,
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter4.isValid()).to.be.false;
+    expect(adhocFilter4.isValid()).toBe(false);
 
     const adhocFilter5 = new AdhocFilter({
       expressionType: EXPRESSION_TYPES.SIMPLE,
@@ -130,7 +128,7 @@ describe('AdhocFilter', () => {
       clause: CLAUSES.WHERE,
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocFilter5.isValid()).to.be.true;
+    expect(adhocFilter5.isValid()).toBe(true);
   });
 
   it('can translate from simple expressions to sql expressions', () => {
@@ -141,7 +139,7 @@ describe('AdhocFilter', () => {
       comparator: '10',
       clause: CLAUSES.WHERE,
     });
-    expect(adhocFilter1.translateToSql()).to.equal('value = 10');
+    expect(adhocFilter1.translateToSql()).toBe('value = 10');
 
     const adhocFilter2 = new AdhocFilter({
       expressionType: EXPRESSION_TYPES.SIMPLE,
@@ -150,6 +148,6 @@ describe('AdhocFilter', () => {
       comparator: '5',
       clause: CLAUSES.HAVING,
     });
-    expect(adhocFilter2.translateToSql()).to.equal('SUM(value) <> 5');
+    expect(adhocFilter2.translateToSql()).toBe('SUM(value) <> 5');
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js b/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js
index fe6797c..c52e601 100644
--- a/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js
+++ b/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import AdhocMetric, { EXPRESSION_TYPES } from '../../../src/explore/AdhocMetric';
 import { AGGREGATES } from '../../../src/explore/constants';
 
@@ -11,8 +9,8 @@ describe('AdhocMetric', () => {
       column: valueColumn,
       aggregate: AGGREGATES.SUM,
     });
-    expect(adhocMetric.optionName.length).to.be.above(10);
-    expect(adhocMetric).to.deep.equal({
+    expect(adhocMetric.optionName.length).toBeGreaterThan(10);
+    expect(adhocMetric).toEqual({
       expressionType: EXPRESSION_TYPES.SIMPLE,
       column: valueColumn,
       aggregate: AGGREGATES.SUM,
@@ -31,11 +29,11 @@ describe('AdhocMetric', () => {
     });
     const adhocMetric2 = adhocMetric1.duplicateWith({ aggregate: AGGREGATES.AVG });
 
-    expect(adhocMetric1.column).to.equal(adhocMetric2.column);
-    expect(adhocMetric1.column).to.equal(valueColumn);
+    expect(adhocMetric1.column).toBe(adhocMetric2.column);
+    expect(adhocMetric1.column).toBe(valueColumn);
 
-    expect(adhocMetric1.aggregate).to.equal(AGGREGATES.SUM);
-    expect(adhocMetric2.aggregate).to.equal(AGGREGATES.AVG);
+    expect(adhocMetric1.aggregate).toBe(AGGREGATES.SUM);
+    expect(adhocMetric2.aggregate).toBe(AGGREGATES.AVG);
   });
 
   it('can verify equality', () => {
@@ -46,7 +44,7 @@ describe('AdhocMetric', () => {
     const adhocMetric2 = adhocMetric1.duplicateWith({});
 
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric1.equals(adhocMetric2)).to.be.true;
+    expect(adhocMetric1.equals(adhocMetric2)).toBe(true);
   });
 
   it('can verify inequality', () => {
@@ -59,7 +57,7 @@ describe('AdhocMetric', () => {
     const adhocMetric2 = adhocMetric1.duplicateWith({ label: 'new label' });
 
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric1.equals(adhocMetric2)).to.be.false;
+    expect(adhocMetric1.equals(adhocMetric2)).toBe(false);
 
     const adhocMetric3 = new AdhocMetric({
       expressionType: EXPRESSION_TYPES.SQL,
@@ -70,7 +68,7 @@ describe('AdhocMetric', () => {
     const adhocMetric4 = adhocMetric3.duplicateWith({ sqlExpression: 'COUNT(1)' });
 
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric3.equals(adhocMetric4)).to.be.false;
+    expect(adhocMetric3.equals(adhocMetric4)).toBe(false);
   });
 
   it('updates label if hasCustomLabel is false', () => {
@@ -80,7 +78,7 @@ describe('AdhocMetric', () => {
     });
     const adhocMetric2 = adhocMetric1.duplicateWith({ aggregate: AGGREGATES.AVG });
 
-    expect(adhocMetric2.label).to.equal('AVG(value)');
+    expect(adhocMetric2.label).toBe('AVG(value)');
   });
 
   it('keeps label if hasCustomLabel is true', () => {
@@ -92,7 +90,7 @@ describe('AdhocMetric', () => {
     });
     const adhocMetric2 = adhocMetric1.duplicateWith({ aggregate: AGGREGATES.AVG });
 
-    expect(adhocMetric2.label).to.equal('label1');
+    expect(adhocMetric2.label).toBe('label1');
   });
 
   it('can determine if it is valid', () => {
@@ -104,7 +102,7 @@ describe('AdhocMetric', () => {
       label: 'label1',
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric1.isValid()).to.be.true;
+    expect(adhocMetric1.isValid()).toBe(true);
 
     const adhocMetric2 = new AdhocMetric({
       expressionType: EXPRESSION_TYPES.SIMPLE,
@@ -114,7 +112,7 @@ describe('AdhocMetric', () => {
       label: 'label1',
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric2.isValid()).to.be.false;
+    expect(adhocMetric2.isValid()).toBe(false);
 
     const adhocMetric3 = new AdhocMetric({
       expressionType: EXPRESSION_TYPES.SQL,
@@ -123,7 +121,7 @@ describe('AdhocMetric', () => {
       label: 'label1',
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric3.isValid()).to.be.true;
+    expect(adhocMetric3.isValid()).toBe(true);
 
     const adhocMetric4 = new AdhocMetric({
       expressionType: EXPRESSION_TYPES.SQL,
@@ -133,7 +131,7 @@ describe('AdhocMetric', () => {
       label: 'label1',
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric4.isValid()).to.be.false;
+    expect(adhocMetric4.isValid()).toBe(false);
 
     const adhocMetric5 = new AdhocMetric({
       expressionType: EXPRESSION_TYPES.SQL,
@@ -141,48 +139,54 @@ describe('AdhocMetric', () => {
       label: 'label1',
     });
     // eslint-disable-next-line no-unused-expressions
-    expect(adhocMetric5.isValid()).to.be.false;
-  });
-
-  it('can translate back from sql expressions to simple expressions when possible', () => {
-    const adhocMetric = new AdhocMetric({
-      expressionType: EXPRESSION_TYPES.SQL,
-      sqlExpression: 'AVG(my_column)',
-      hasCustomLabel: true,
-      label: 'label1',
-    });
-    expect(adhocMetric.inferSqlExpressionColumn()).to.equal('my_column');
-    expect(adhocMetric.inferSqlExpressionAggregate()).to.equal('AVG');
-
-    const adhocMetric2 = new AdhocMetric({
-      expressionType: EXPRESSION_TYPES.SQL,
-      sqlExpression: 'AVG(SUM(my_column)) / MAX(other_column)',
-      hasCustomLabel: true,
-      label: 'label1',
-    });
-    expect(adhocMetric2.inferSqlExpressionColumn()).to.equal(null);
-    expect(adhocMetric2.inferSqlExpressionAggregate()).to.equal(null);
+    expect(adhocMetric5.isValid()).toBe(false);
   });
 
-  it('will infer columns and aggregates when converting to a simple expression', () => {
-    const adhocMetric = new AdhocMetric({
-      expressionType: EXPRESSION_TYPES.SQL,
-      sqlExpression: 'AVG(my_column)',
-      hasCustomLabel: true,
-      label: 'label1',
-    });
-    const adhocMetric2 = adhocMetric.duplicateWith({
-      expressionType: EXPRESSION_TYPES.SIMPLE,
-      aggregate: AGGREGATES.SUM,
-    });
-    expect(adhocMetric2.aggregate).to.equal(AGGREGATES.SUM);
-    expect(adhocMetric2.column.column_name).to.equal('my_column');
-
-    const adhocMetric3 = adhocMetric.duplicateWith({
-      expressionType: EXPRESSION_TYPES.SIMPLE,
-      column: valueColumn,
-    });
-    expect(adhocMetric3.aggregate).to.equal(AGGREGATES.AVG);
-    expect(adhocMetric3.column.column_name).to.equal('value');
-  });
+  it(
+    'can translate back from sql expressions to simple expressions when possible',
+    () => {
+      const adhocMetric = new AdhocMetric({
+        expressionType: EXPRESSION_TYPES.SQL,
+        sqlExpression: 'AVG(my_column)',
+        hasCustomLabel: true,
+        label: 'label1',
+      });
+      expect(adhocMetric.inferSqlExpressionColumn()).toBe('my_column');
+      expect(adhocMetric.inferSqlExpressionAggregate()).toBe('AVG');
+
+      const adhocMetric2 = new AdhocMetric({
+        expressionType: EXPRESSION_TYPES.SQL,
+        sqlExpression: 'AVG(SUM(my_column)) / MAX(other_column)',
+        hasCustomLabel: true,
+        label: 'label1',
+      });
+      expect(adhocMetric2.inferSqlExpressionColumn()).toBeNull();
+      expect(adhocMetric2.inferSqlExpressionAggregate()).toBeNull();
+    },
+  );
+
+  it(
+    'will infer columns and aggregates when converting to a simple expression',
+    () => {
+      const adhocMetric = new AdhocMetric({
+        expressionType: EXPRESSION_TYPES.SQL,
+        sqlExpression: 'AVG(my_column)',
+        hasCustomLabel: true,
+        label: 'label1',
+      });
+      const adhocMetric2 = adhocMetric.duplicateWith({
+        expressionType: EXPRESSION_TYPES.SIMPLE,
+        aggregate: AGGREGATES.SUM,
+      });
+      expect(adhocMetric2.aggregate).toBe(AGGREGATES.SUM);
+      expect(adhocMetric2.column.column_name).toBe('my_column');
+
+      const adhocMetric3 = adhocMetric.duplicateWith({
+        expressionType: EXPRESSION_TYPES.SIMPLE,
+        column: valueColumn,
+      });
+      expect(adhocMetric3.aggregate).toBe(AGGREGATES.AVG);
+      expect(adhocMetric3.column.column_name).toBe('value');
+    },
+  );
 });
diff --git a/superset/assets/spec/javascripts/explore/chartActions_spec.js b/superset/assets/spec/javascripts/explore/chartActions_spec.js
index 6d8b009..e3a621b 100644
--- a/superset/assets/spec/javascripts/explore/chartActions_spec.js
+++ b/superset/assets/spec/javascripts/explore/chartActions_spec.js
@@ -1,4 +1,3 @@
-import { expect } from 'chai';
 import sinon from 'sinon';
 import $ from 'jquery';
 import * as exploreUtils from '../../../src/explore/exploreUtils';
@@ -31,8 +30,8 @@ describe('chart actions', () => {
       },
     }));
     promise.then(() => {
-      expect(dispatch.callCount).to.equal(3);
-      expect(dispatch.args[0][0].type).to.equal(actions.CHART_UPDATE_TIMEOUT);
+      expect(dispatch.callCount).toBe(3);
+      expect(dispatch.args[0][0].type).toBe(actions.CHART_UPDATE_TIMEOUT);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx
index f385625..ca341be 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../../../../src/explore/AdhocFilter';
@@ -55,7 +54,7 @@ function setup(overrides) {
 describe('AdhocFilterControl', () => {
   it('renders an onPasteSelect', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(OnPasteSelect)).to.have.lengthOf(1);
+    expect(wrapper.find(OnPasteSelect)).toHaveLength(1);
   });
 
   it('handles saved metrics being selected to filter on', () => {
@@ -64,7 +63,7 @@ describe('AdhocFilterControl', () => {
     select.simulate('change', [{ saved_metric_name: 'sum__value' }]);
 
     const adhocFilter = onChange.lastCall.args[0][0];
-    expect(adhocFilter instanceof AdhocFilter).to.be.true;
+    expect(adhocFilter instanceof AdhocFilter).toBe(true);
     expect(adhocFilter.equals((
       new AdhocFilter({
         expressionType: EXPRESSION_TYPES.SQL,
@@ -73,7 +72,7 @@ describe('AdhocFilterControl', () => {
         comparator: 0,
         clause: CLAUSES.HAVING,
       })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('handles adhoc metrics being selected to filter on', () => {
@@ -82,7 +81,7 @@ describe('AdhocFilterControl', () => {
     select.simulate('change', [sumValueAdhocMetric]);
 
     const adhocFilter = onChange.lastCall.args[0][0];
-    expect(adhocFilter instanceof AdhocFilter).to.be.true;
+    expect(adhocFilter instanceof AdhocFilter).toBe(true);
     expect(adhocFilter.equals((
       new AdhocFilter({
         expressionType: EXPRESSION_TYPES.SQL,
@@ -91,7 +90,7 @@ describe('AdhocFilterControl', () => {
         comparator: 0,
         clause: CLAUSES.HAVING,
       })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('handles columns being selected to filter on', () => {
@@ -100,7 +99,7 @@ describe('AdhocFilterControl', () => {
     select.simulate('change', [columns[0]]);
 
     const adhocFilter = onChange.lastCall.args[0][0];
-    expect(adhocFilter instanceof AdhocFilter).to.be.true;
+    expect(adhocFilter instanceof AdhocFilter).toBe(true);
     expect(adhocFilter.equals((
       new AdhocFilter({
         expressionType: EXPRESSION_TYPES.SIMPLE,
@@ -109,7 +108,7 @@ describe('AdhocFilterControl', () => {
         comparator: '',
         clause: CLAUSES.WHERE,
       })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('persists existing filters even when new filters are added', () => {
@@ -118,11 +117,11 @@ describe('AdhocFilterControl', () => {
     select.simulate('change', [simpleAdhocFilter, columns[0]]);
 
     const existingAdhocFilter = onChange.lastCall.args[0][0];
-    expect(existingAdhocFilter instanceof AdhocFilter).to.be.true;
-    expect(existingAdhocFilter.equals(simpleAdhocFilter)).to.be.true;
+    expect(existingAdhocFilter instanceof AdhocFilter).toBe(true);
+    expect(existingAdhocFilter.equals(simpleAdhocFilter)).toBe(true);
 
     const newAdhocFilter = onChange.lastCall.args[0][1];
-    expect(newAdhocFilter instanceof AdhocFilter).to.be.true;
+    expect(newAdhocFilter instanceof AdhocFilter).toBe(true);
     expect(newAdhocFilter.equals((
       new AdhocFilter({
         expressionType: EXPRESSION_TYPES.SIMPLE,
@@ -131,6 +130,6 @@ describe('AdhocFilterControl', () => {
         comparator: '',
         clause: CLAUSES.WHERE,
       })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx
index 7c99c6c..31f4c2b 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { FormGroup } from 'react-bootstrap';
 
@@ -58,67 +57,67 @@ function setup(overrides) {
 describe('AdhocFilterEditPopoverSimpleTabContent', () => {
   it('renders the simple tab form', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(FormGroup)).to.have.lengthOf(3);
+    expect(wrapper.find(FormGroup)).toHaveLength(3);
   });
 
   it('passes the new adhocFilter to onChange after onSubjectChange', () => {
     const { wrapper, onChange } = setup();
     wrapper.instance().onSubjectChange({ type: 'VARCHAR(255)', column_name: 'source' });
-    expect(onChange.calledOnce).to.be.true;
+    expect(onChange.calledOnce).toBe(true);
     expect(onChange.lastCall.args[0].equals((
       simpleAdhocFilter.duplicateWith({ subject: 'source' })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('may alter the clause in onSubjectChange if the old clause is not appropriate', () => {
     const { wrapper, onChange } = setup();
     wrapper.instance().onSubjectChange(sumValueAdhocMetric);
-    expect(onChange.calledOnce).to.be.true;
+    expect(onChange.calledOnce).toBe(true);
     expect(onChange.lastCall.args[0].equals((
       simpleAdhocFilter.duplicateWith({
         subject: sumValueAdhocMetric.label,
         clause: CLAUSES.HAVING,
       })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('will convert from individual comparator to array if the operator changes to multi', () => {
     const { wrapper, onChange } = setup();
     wrapper.instance().onOperatorChange({ operator: 'in' });
-    expect(onChange.calledOnce).to.be.true;
-    expect(onChange.lastCall.args[0].comparator).to.have.lengthOf(1);
-    expect(onChange.lastCall.args[0].comparator[0]).to.equal('10');
-    expect(onChange.lastCall.args[0].operator).to.equal('in');
+    expect(onChange.calledOnce).toBe(true);
+    expect(onChange.lastCall.args[0].comparator).toHaveLength(1);
+    expect(onChange.lastCall.args[0].comparator[0]).toBe('10');
+    expect(onChange.lastCall.args[0].operator).toBe('in');
   });
 
   it('will convert from array to individual comparators if the operator changes from multi', () => {
     const { wrapper, onChange } = setup({ adhocFilter: simpleMultiAdhocFilter });
     wrapper.instance().onOperatorChange({ operator: '<' });
-    expect(onChange.calledOnce).to.be.true;
+    expect(onChange.calledOnce).toBe(true);
     expect(onChange.lastCall.args[0].equals((
       simpleAdhocFilter.duplicateWith({ operator: '<', comparator: '10' })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('passes the new adhocFilter to onChange after onComparatorChange', () => {
     const { wrapper, onChange } = setup();
     wrapper.instance().onComparatorChange('20');
-    expect(onChange.calledOnce).to.be.true;
+    expect(onChange.calledOnce).toBe(true);
     expect(onChange.lastCall.args[0].equals((
       simpleAdhocFilter.duplicateWith({ comparator: '20' })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('will filter operators for table datasources', () => {
     const { wrapper } = setup({ datasource: { type: 'table' } });
-    expect(wrapper.instance().isOperatorRelevant('regex')).to.be.false;
-    expect(wrapper.instance().isOperatorRelevant('LIKE')).to.be.true;
+    expect(wrapper.instance().isOperatorRelevant('regex')).toBe(false);
+    expect(wrapper.instance().isOperatorRelevant('LIKE')).toBe(true);
   });
 
   it('will filter operators for druid datasources', () => {
     const { wrapper } = setup({ datasource: { type: 'druid' } });
-    expect(wrapper.instance().isOperatorRelevant('regex')).to.be.true;
-    expect(wrapper.instance().isOperatorRelevant('LIKE')).to.be.false;
+    expect(wrapper.instance().isOperatorRelevant('regex')).toBe(true);
+    expect(wrapper.instance().isOperatorRelevant('LIKE')).toBe(false);
   });
 
   it('expands when its multi comparator input field expands', () => {
@@ -128,7 +127,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
       { _selectRef: { select: { control: { clientHeight: 57 } } } };
     wrapper.instance().handleMultiComparatorInputHeightChange();
 
-    expect(onHeightChange.calledOnce).to.be.true;
-    expect(onHeightChange.lastCall.args[0]).to.equal(27);
+    expect(onHeightChange.calledOnce).toBe(true);
+    expect(onHeightChange.lastCall.args[0]).toBe(27);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx
index c3766bf..6365e06 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { FormGroup } from 'react-bootstrap';
 
@@ -30,24 +29,24 @@ function setup(overrides) {
 describe('AdhocFilterEditPopoverSqlTabContent', () => {
   it('renders the sql tab form', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(FormGroup)).to.have.lengthOf(2);
+    expect(wrapper.find(FormGroup)).toHaveLength(2);
   });
 
   it('passes the new clause to onChange after onSqlExpressionClauseChange', () => {
     const { wrapper, onChange } = setup();
     wrapper.instance().onSqlExpressionClauseChange(CLAUSES.HAVING);
-    expect(onChange.calledOnce).to.be.true;
+    expect(onChange.calledOnce).toBe(true);
     expect(onChange.lastCall.args[0].equals((
       sqlAdhocFilter.duplicateWith({ clause: CLAUSES.HAVING })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 
   it('passes the new query to onChange after onSqlExpressionChange', () => {
     const { wrapper, onChange } = setup();
     wrapper.instance().onSqlExpressionChange('value < 5');
-    expect(onChange.calledOnce).to.be.true;
+    expect(onChange.calledOnce).toBe(true);
     expect(onChange.lastCall.args[0].equals((
       sqlAdhocFilter.duplicateWith({ sqlExpression: 'value < 5' })
-    ))).to.be.true;
+    ))).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx
index 23d3b9a..2fc51f3 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Button, Popover, Tab, Tabs } from 'react-bootstrap';
 
@@ -60,42 +59,42 @@ function setup(overrides) {
 describe('AdhocFilterEditPopover', () => {
   it('renders simple tab content by default', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(Popover)).to.have.lengthOf(1);
-    expect(wrapper.find(Tabs)).to.have.lengthOf(1);
-    expect(wrapper.find(Tab)).to.have.lengthOf(2);
-    expect(wrapper.find(Button)).to.have.lengthOf(2);
-    expect(wrapper.find(AdhocFilterEditPopoverSimpleTabContent)).to.have.lengthOf(1);
+    expect(wrapper.find(Popover)).toHaveLength(1);
+    expect(wrapper.find(Tabs)).toHaveLength(1);
+    expect(wrapper.find(Tab)).toHaveLength(2);
+    expect(wrapper.find(Button)).toHaveLength(2);
+    expect(wrapper.find(AdhocFilterEditPopoverSimpleTabContent)).toHaveLength(1);
   });
 
   it('renders sql tab content when the adhoc filter expressionType is sql', () => {
     const { wrapper } = setup({ adhocFilter: sqlAdhocFilter });
-    expect(wrapper.find(Popover)).to.have.lengthOf(1);
-    expect(wrapper.find(Tabs)).to.have.lengthOf(1);
-    expect(wrapper.find(Tab)).to.have.lengthOf(2);
-    expect(wrapper.find(Button)).to.have.lengthOf(2);
-    expect(wrapper.find(AdhocFilterEditPopoverSqlTabContent)).to.have.lengthOf(1);
+    expect(wrapper.find(Popover)).toHaveLength(1);
+    expect(wrapper.find(Tabs)).toHaveLength(1);
+    expect(wrapper.find(Tab)).toHaveLength(2);
+    expect(wrapper.find(Button)).toHaveLength(2);
+    expect(wrapper.find(AdhocFilterEditPopoverSqlTabContent)).toHaveLength(1);
   });
 
   it('overwrites the adhocFilter in state with onAdhocFilterChange', () => {
     const { wrapper } = setup();
     wrapper.instance().onAdhocFilterChange(sqlAdhocFilter);
-    expect(wrapper.state('adhocFilter')).to.deep.equal(sqlAdhocFilter);
+    expect(wrapper.state('adhocFilter')).toEqual(sqlAdhocFilter);
   });
 
   it('prevents saving if the filter is invalid', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(Button).find({ disabled: true })).to.have.lengthOf(0);
+    expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
     wrapper.instance().onAdhocFilterChange(simpleAdhocFilter.duplicateWith({ operator: null }));
-    expect(wrapper.find(Button).find({ disabled: true })).to.have.lengthOf(1);
+    expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(1);
     wrapper.instance().onAdhocFilterChange(sqlAdhocFilter);
-    expect(wrapper.find(Button).find({ disabled: true })).to.have.lengthOf(0);
+    expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
   });
 
   it('highlights save if changes are present', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).to.have.lengthOf(0);
+    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(0);
     wrapper.instance().onAdhocFilterChange(sqlAdhocFilter);
-    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).to.have.lengthOf(1);
+    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(1);
   });
 
   it('will initiate a drag when clicked', () => {
@@ -103,9 +102,9 @@ describe('AdhocFilterEditPopover', () => {
     wrapper.instance().onDragDown = sinon.spy();
     wrapper.instance().forceUpdate();
 
-    expect(wrapper.find('i.glyphicon-resize-full')).to.have.lengthOf(1);
-    expect(wrapper.instance().onDragDown.calledOnce).to.be.false;
+    expect(wrapper.find('i.glyphicon-resize-full')).toHaveLength(1);
+    expect(wrapper.instance().onDragDown.calledOnce).toBe(false);
     wrapper.find('i.glyphicon-resize-full').simulate('mouseDown', {});
-    expect(wrapper.instance().onDragDown.calledOnce).to.be.true;
+    expect(wrapper.instance().onDragDown.calledOnce).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx
index 9d2e2d3..e0e6178 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Label, OverlayTrigger } from 'react-bootstrap';
 
@@ -32,7 +31,7 @@ function setup(overrides) {
 describe('AdhocFilterOption', () => {
   it('renders an overlay trigger wrapper for the label', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(OverlayTrigger)).to.have.lengthOf(1);
-    expect(wrapper.find(Label)).to.have.lengthOf(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
+    expect(wrapper.find(Label)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx
index d845cc5..2e833ac 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { OverlayTrigger } from 'react-bootstrap';
 
@@ -34,14 +33,14 @@ function setup(overrides) {
 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).find('span').text()).to.equal('My Metric\xa0');
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
+    expect(wrapper.find(OverlayTrigger).find('span').text()).toBe('My Metric\xa0');
   });
 
   it('transfers to edit mode when clicked', () => {
     const { wrapper } = setup();
-    expect(wrapper.state('isEditable')).to.be.false;
+    expect(wrapper.state('isEditable')).toBe(false);
     wrapper.simulate('click');
-    expect(wrapper.state('isEditable')).to.be.true;
+    expect(wrapper.state('isEditable')).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx
index 5f03c2a..07822b2 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Button, FormGroup, Popover } from 'react-bootstrap';
 
@@ -43,60 +42,60 @@ function setup(overrides) {
 describe('AdhocMetricEditPopover', () => {
   it('renders a popover with edit metric form contents', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(Popover)).to.have.lengthOf(1);
-    expect(wrapper.find(FormGroup)).to.have.lengthOf(3);
-    expect(wrapper.find(Button)).to.have.lengthOf(2);
+    expect(wrapper.find(Popover)).toHaveLength(1);
+    expect(wrapper.find(FormGroup)).toHaveLength(3);
+    expect(wrapper.find(Button)).toHaveLength(2);
   });
 
   it('overwrites the adhocMetric in state with onColumnChange', () => {
     const { wrapper } = setup();
     wrapper.instance().onColumnChange(columns[0]);
-    expect(wrapper.state('adhocMetric')).to.deep.equal(sumValueAdhocMetric.duplicateWith({ column: columns[0] }));
+    expect(wrapper.state('adhocMetric')).toEqual(sumValueAdhocMetric.duplicateWith({ column: columns[0] }));
   });
 
   it('overwrites the adhocMetric in state with onAggregateChange', () => {
     const { wrapper } = setup();
     wrapper.instance().onAggregateChange({ aggregate: AGGREGATES.AVG });
-    expect(wrapper.state('adhocMetric')).to.deep.equal(sumValueAdhocMetric.duplicateWith({ aggregate: AGGREGATES.AVG }));
+    expect(wrapper.state('adhocMetric')).toEqual(sumValueAdhocMetric.duplicateWith({ aggregate: AGGREGATES.AVG }));
   });
 
   it('overwrites the adhocMetric in state with onSqlExpressionChange', () => {
     const { wrapper } = setup({ adhocMetric: sqlExpressionAdhocMetric });
     wrapper.instance().onSqlExpressionChange('COUNT(1)');
-    expect(wrapper.state('adhocMetric')).to.deep.equal(sqlExpressionAdhocMetric.duplicateWith({ sqlExpression: 'COUNT(1)' }));
+    expect(wrapper.state('adhocMetric')).toEqual(sqlExpressionAdhocMetric.duplicateWith({ sqlExpression: 'COUNT(1)' }));
   });
 
   it('overwrites the adhocMetric in state with onLabelChange', () => {
     const { wrapper } = setup();
     wrapper.instance().onLabelChange({ target: { value: 'new label' } });
-    expect(wrapper.state('adhocMetric').label).to.equal('new label');
-    expect(wrapper.state('adhocMetric').hasCustomLabel).to.be.true;
+    expect(wrapper.state('adhocMetric').label).toBe('new label');
+    expect(wrapper.state('adhocMetric').hasCustomLabel).toBe(true);
   });
 
   it('returns to default labels when the custom label is cleared', () => {
     const { wrapper } = setup();
     wrapper.instance().onLabelChange({ target: { value: 'new label' } });
     wrapper.instance().onLabelChange({ target: { value: '' } });
-    expect(wrapper.state('adhocMetric').label).to.equal('SUM(value)');
-    expect(wrapper.state('adhocMetric').hasCustomLabel).to.be.false;
+    expect(wrapper.state('adhocMetric').label).toBe('SUM(value)');
+    expect(wrapper.state('adhocMetric').hasCustomLabel).toBe(false);
   });
 
   it('prevents saving if no column or aggregate is chosen', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(Button).find({ disabled: true })).to.have.lengthOf(0);
+    expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
     wrapper.instance().onColumnChange(null);
-    expect(wrapper.find(Button).find({ disabled: true })).to.have.lengthOf(1);
+    expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(1);
     wrapper.instance().onColumnChange({ column: columns[0] });
-    expect(wrapper.find(Button).find({ disabled: true })).to.have.lengthOf(0);
+    expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
     wrapper.instance().onAggregateChange(null);
-    expect(wrapper.find(Button).find({ disabled: true })).to.have.lengthOf(1);
+    expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(1);
   });
 
   it('highlights save if changes are present', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).to.have.lengthOf(0);
+    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(0);
     wrapper.instance().onColumnChange({ column: columns[1] });
-    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).to.have.lengthOf(1);
+    expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(1);
   });
 
   it('will initiate a drag when clicked', () => {
@@ -104,9 +103,9 @@ describe('AdhocMetricEditPopover', () => {
     wrapper.instance().onDragDown = sinon.spy();
     wrapper.instance().forceUpdate();
 
-    expect(wrapper.find('i.glyphicon-resize-full')).to.have.lengthOf(1);
-    expect(wrapper.instance().onDragDown.calledOnce).to.be.false;
+    expect(wrapper.find('i.glyphicon-resize-full')).toHaveLength(1);
+    expect(wrapper.instance().onDragDown.calledOnce).toBe(false);
     wrapper.find('i.glyphicon-resize-full').simulate('mouseDown');
-    expect(wrapper.instance().onDragDown.calledOnce).to.be.true;
+    expect(wrapper.instance().onDragDown.calledOnce).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx
index 47c09f9..8a0f263 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Label, OverlayTrigger } from 'react-bootstrap';
 
@@ -35,7 +34,7 @@ function setup(overrides) {
 describe('AdhocMetricOption', () => {
   it('renders an overlay trigger wrapper for the label', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(OverlayTrigger)).to.have.lengthOf(1);
-    expect(wrapper.find(Label)).to.have.lengthOf(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
+    expect(wrapper.find(Label)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx
index b0e426c..d19d614 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import AdhocMetricStaticOption from '../../../../src/explore/components/AdhocMetricStaticOption';
@@ -16,6 +15,6 @@ const sumValueAdhocMetric = new AdhocMetric({
 describe('AdhocMetricStaticOption', () => {
   it('renders the adhoc metrics label', () => {
     const wrapper = shallow(<AdhocMetricStaticOption adhocMetric={sumValueAdhocMetric} />);
-    expect(wrapper.text()).to.equal('SUM(source)');
+    expect(wrapper.text()).toBe('SUM(source)');
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx
index e70858b..c662e7d 100644
--- a/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import AggregateOption from '../../../../src/explore/components/AggregateOption';
@@ -8,6 +7,6 @@ import AggregateOption from '../../../../src/explore/components/AggregateOption'
 describe('AggregateOption', () => {
   it('renders the aggregate', () => {
     const wrapper = shallow(<AggregateOption aggregate={{ aggregate_name: 'SUM' }} />);
-    expect(wrapper.text()).to.equal('SUM');
+    expect(wrapper.text()).toBe('SUM');
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx
index 9e98cb6..7881c03 100644
--- a/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx
@@ -2,7 +2,6 @@
 import React from 'react';
 import { FormControl } from 'react-bootstrap';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { mount } from 'enzyme';
 
 import BoundsControl from '../../../../src/explore/components/controls/BoundsControl';
@@ -21,17 +20,17 @@ describe('BoundsControl', () => {
   });
 
   it('renders two FormControls', () => {
-    expect(wrapper.find(FormControl)).to.have.lengthOf(2);
+    expect(wrapper.find(FormControl)).toHaveLength(2);
   });
 
   it('errors on non-numeric', () => {
     wrapper.find(FormControl).first().simulate('change', { target: { value: 's' } });
-    expect(defaultProps.onChange.calledWith([null, null])).to.be.true;
-    expect(defaultProps.onChange.getCall(0).args[1][0]).to.contain('value should be numeric');
+    expect(defaultProps.onChange.calledWith([null, null])).toBe(true);
+    expect(defaultProps.onChange.getCall(0).args[1][0]).toContain('value should be numeric');
   });
   it('casts to numeric', () => {
     wrapper.find(FormControl).first().simulate('change', { target: { value: '1' } });
     wrapper.find(FormControl).last().simulate('change', { target: { value: '5' } });
-    expect(defaultProps.onChange.calledWith([1, 5])).to.be.true;
+    expect(defaultProps.onChange.calledWith([1, 5])).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx
index 27aba7d..62c4863 100644
--- a/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import CheckboxControl from '../../../../src/explore/components/controls/CheckboxControl';
@@ -24,9 +23,9 @@ describe('CheckboxControl', () => {
 
   it('renders a Checkbox', () => {
     const controlHeader = wrapper.find(ControlHeader);
-    expect(controlHeader).to.have.lengthOf(1);
+    expect(controlHeader).toHaveLength(1);
 
     const headerWrapper = controlHeader.shallow();
-    expect(headerWrapper.find(Checkbox)).to.have.length(1);
+    expect(headerWrapper.find(Checkbox)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx
index 1271d62..70f00aa 100644
--- a/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { OverlayTrigger } from 'react-bootstrap';
 import { SketchPicker } from 'react-color';
@@ -26,18 +25,12 @@ describe('ColorPickerControl', () => {
 
   it('renders a OverlayTrigger', () => {
     const controlHeader = wrapper.find(ControlHeader);
-    expect(controlHeader).to.have.lengthOf(1);
-    expect(wrapper.find(OverlayTrigger)).to.have.length(1);
-  });
-
-  it('renders a OverlayTrigger', () => {
-    const controlHeader = wrapper.find(ControlHeader);
-    expect(controlHeader).to.have.lengthOf(1);
-    expect(wrapper.find(OverlayTrigger)).to.have.length(1);
+    expect(controlHeader).toHaveLength(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
   });
 
   it('renders a Popover with a SketchPicker', () => {
     const popOver = shallow(inst.renderPopover());
-    expect(popOver.find(SketchPicker)).to.have.lengthOf(1);
+    expect(popOver.find(SketchPicker)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx b/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx
index 1551286..5cf2be2 100644
--- a/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { mount } from 'enzyme';
 import { Creatable } from 'react-select';
 
@@ -19,6 +18,6 @@ describe('ColorSchemeControl', () => {
   });
 
   it('renders a Creatable', () => {
-    expect(wrapper.find(Creatable)).to.have.length(1);
+    expect(wrapper.find(Creatable)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx b/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx
index 7a9a59d..ca31dc7 100644
--- a/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Panel } from 'react-bootstrap';
 
@@ -25,12 +24,12 @@ describe('ControlPanelSection', () => {
   it('is a valid element', () => {
     expect(
       React.isValidElement(<ControlPanelSection {...defaultProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('renders a Panel component', () => {
     wrapper = shallow(<ControlPanelSection {...defaultProps} />);
-    expect(wrapper.find(Panel)).to.have.length(1);
+    expect(wrapper.find(Panel)).toHaveLength(1);
   });
 
   describe('with optional props', () => {
@@ -40,12 +39,11 @@ describe('ControlPanelSection', () => {
     });
 
     it('renders a label if present', () => {
-      expect(wrapper.find(Panel).dive().text()).to.contain('my label');
+      expect(wrapper.find(Panel).dive().text()).toContain('my label');
     });
 
     it('renders a InfoTooltipWithTrigger if label and tooltip is present', () => {
-      expect(wrapper.find(Panel).dive().find(InfoTooltipWithTrigger))
-        .to.have.length(1);
+      expect(wrapper.find(Panel).dive().find(InfoTooltipWithTrigger)).toHaveLength(1);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx b/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx
index 7f408f9..445a722 100644
--- a/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { getFormDataFromControls, defaultControls }
   from '../../../../src/explore/store';
@@ -25,6 +24,6 @@ describe('ControlPanelsContainer', () => {
   });
 
   it('renders ControlPanelSections', () => {
-    expect(wrapper.find(ControlPanelSection)).to.have.lengthOf(6);
+    expect(wrapper.find(ControlPanelSection)).toHaveLength(6);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx b/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx
index 80fc0d0..962412b 100644
--- a/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx
@@ -1,17 +1,16 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import ControlSetRow from '../../../../src/explore/components/ControlRow';
 
 describe('ControlSetRow', () => {
   it('renders a single row with one element', () => {
     const wrapper = shallow(<ControlSetRow controls={[<a />]} />);
-    expect(wrapper.find('.row')).to.have.lengthOf(1);
-    expect(wrapper.find('.row').find('a')).to.have.lengthOf(1);
+    expect(wrapper.find('.row')).toHaveLength(1);
+    expect(wrapper.find('.row').find('a')).toHaveLength(1);
   });
   it('renders a single row with two elements', () => {
     const wrapper = shallow(<ControlSetRow controls={[<a />, <a />]} />);
-    expect(wrapper.find('.row')).to.have.lengthOf(1);
-    expect(wrapper.find('.row').find('a')).to.have.lengthOf(2);
+    expect(wrapper.find('.row')).toHaveLength(1);
+    expect(wrapper.find('.row').find('a')).toHaveLength(2);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
index 7f28bf3..ab70ea8 100644
--- a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import sinon from 'sinon';
 import configureStore from 'redux-mock-store';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import DatasourceModal from '../../../../src/datasource/DatasourceModal';
 import DatasourceControl from '../../../../src/explore/components/controls/DatasourceControl';
@@ -34,6 +33,6 @@ describe('DatasourceControl', () => {
 
   it('renders a Modal', () => {
     const wrapper = setup();
-    expect(wrapper.find(DatasourceModal)).to.have.lengthOf(1);
+    expect(wrapper.find(DatasourceModal)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx
index 2230d97..fec21ec 100644
--- a/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Button, Label } from 'react-bootstrap';
 
@@ -25,37 +24,37 @@ describe('DateFilterControl', () => {
 
   it('renders a ControlHeader', () => {
     const controlHeader = wrapper.find(ControlHeader);
-    expect(controlHeader).to.have.lengthOf(1);
+    expect(controlHeader).toHaveLength(1);
   });
   it('renders 3 Buttons', () => {
     const label = wrapper.find(Label).first();
     label.simulate('click');
     setTimeout(() => {
-      expect(wrapper.find(Button)).to.have.length(3);
+      expect(wrapper.find(Button)).toHaveLength(3);
     }, 10);
   });
   it('loads the right state', () => {
     const label = wrapper.find(Label).first();
     label.simulate('click');
     setTimeout(() => {
-      expect(wrapper.state().num).to.equal('90');
+      expect(wrapper.state().num).toBe('90');
     }, 10);
   });
   it('renders 2 dimmed sections', () => {
     const label = wrapper.find(Label).first();
     label.simulate('click');
     setTimeout(() => {
-      expect(wrapper.find(Button)).to.have.length(3);
+      expect(wrapper.find(Button)).toHaveLength(3);
     }, 10);
   });
   it('opens and closes', () => {
     const label = wrapper.find(Label).first();
     label.simulate('click');
     setTimeout(() => {
-      expect(wrapper.find('.popover')).to.have.length(1);
+      expect(wrapper.find('.popover')).toHaveLength(1);
       expect(wrapper.find('.ok')).first().simulate('click');
       setTimeout(() => {
-        expect(wrapper.find('.popover')).to.have.length(0);
+        expect(wrapper.find('.popover')).toHaveLength(0);
       }, 10);
     }, 10);
   });
diff --git a/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx
index 68c9c41..9b48611 100644
--- a/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { mount } from 'enzyme';
 import ModalTrigger from './../../../../src/components/ModalTrigger';
 
@@ -20,10 +19,10 @@ describe('DisplayQueryButton', () => {
   };
 
   it('is valid', () => {
-    expect(React.isValidElement(<DisplayQueryButton {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<DisplayQueryButton {...defaultProps} />)).toBe(true);
   });
   it('renders a dropdown', () => {
     const wrapper = mount(<DisplayQueryButton {...defaultProps} />);
-    expect(wrapper.find(ModalTrigger)).to.have.lengthOf(3);
+    expect(wrapper.find(ModalTrigger)).toHaveLength(3);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
index 896b310..9879750 100644
--- a/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow, mount } from 'enzyme';
 import { OverlayTrigger } from 'react-bootstrap';
 import sinon from 'sinon';
@@ -13,12 +12,12 @@ describe('EmbedCodeButton', () => {
   };
 
   it('renders', () => {
-    expect(React.isValidElement(<EmbedCodeButton {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<EmbedCodeButton {...defaultProps} />)).toBe(true);
   });
 
   it('renders overlay trigger', () => {
     const wrapper = shallow(<EmbedCodeButton {...defaultProps} />);
-    expect(wrapper.find(OverlayTrigger)).to.have.length(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
   });
 
   it('returns correct embed code', () => {
@@ -35,11 +34,11 @@ describe('EmbedCodeButton', () => {
       '  seamless\n' +
       '  frameBorder="0"\n' +
       '  scrolling="no"\n' +
-      '  src="nullendpoint_url&height=1000"\n' +
+      '  src="http://localhostendpoint_url&height=1000"\n' +
       '>\n' +
       '</iframe>'
     );
-    expect(wrapper.instance().generateEmbedHTML()).to.equal(embedHTML);
+    expect(wrapper.instance().generateEmbedHTML()).toBe(embedHTML);
     stub.restore();
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx b/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx
index 933e0df..e85a05a 100644
--- a/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import ExploreActionButtons from
   '../../../../src/explore/components/ExploreActionButtons';
@@ -15,11 +14,11 @@ describe('ExploreActionButtons', () => {
   it('renders', () => {
     expect(
       React.isValidElement(<ExploreActionButtons {...defaultProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('should render 5 children/buttons', () => {
     const wrapper = shallow(<ExploreActionButtons {...defaultProps} />);
-    expect(wrapper.children()).to.have.length(5);
+    expect(wrapper.children()).toHaveLength(5);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx b/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx
index b354b8d..9087243 100644
--- a/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import ExploreChartHeader from '../../../../src/explore/components/ExploreChartHeader';
@@ -29,11 +28,11 @@ describe('ExploreChartHeader', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<ExploreChartHeader {...mockProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('renders', () => {
-    expect(wrapper.find(EditableTitle)).to.have.lengthOf(1);
-    expect(wrapper.find(ExploreActionButtons)).to.have.lengthOf(1);
+    expect(wrapper.find(EditableTitle)).toHaveLength(1);
+    expect(wrapper.find(ExploreActionButtons)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.js b/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.js
deleted file mode 100644
index 0d82de5..0000000
--- a/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// this test must be commented out because ChartContainer is now importing files
-// from visualizations/*.js which are also importing css files which breaks in the testing env
-
-// import React from 'react';
-// import { expect } from 'chai';
-//
-// import ChartContainer from '../../../../src/explore/components/ChartContainer';
-
-// describe('ChartContainer', () => {
-//   const mockProps = {
-//     sliceName: 'Trend Line',
-//     vizType: 'line',
-//     height: '500px',
-//   };
-
-//   it('renders when vizType is line', () => {
-//     expect(
-//       React.isValidElement(<ChartContainer {...mockProps} />)
-//     ).to.equal(true);
-//   });
-// });
diff --git a/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.jsx b/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.jsx
new file mode 100644
index 0000000..fdd2e61
--- /dev/null
+++ b/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.jsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+import ChartContainer from '../../../../src/explore/components/ExploreChartPanel';
+
+describe('ChartContainer', () => {
+  const mockProps = {
+    sliceName: 'Trend Line',
+    vizType: 'line',
+    height: '500px',
+  };
+
+  it('renders when vizType is line', () => {
+    expect(
+      React.isValidElement(<ChartContainer {...mockProps} />),
+    ).toBe(true);
+  });
+});
diff --git a/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx b/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx
index 525a85b..1b4f77a 100644
--- a/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import getInitialState from '../../../../src/explore/reducers/getInitialState';
@@ -20,7 +19,7 @@ describe('ExploreViewContainer', () => {
   let store;
   let wrapper;
 
-  before(() => {
+  beforeAll(() => {
     const bootstrapData = {
       common: {
         feature_flags: {
@@ -46,24 +45,24 @@ describe('ExploreViewContainer', () => {
   });
 
   it('should set feature flags', () => {
-    expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).to.equal(true);
+    expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).toBe(true);
   });
 
   it('renders', () => {
     expect(
       React.isValidElement(<ExploreViewContainer />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('renders QueryAndSaveButtons', () => {
-    expect(wrapper.dive().find(QueryAndSaveBtns)).to.have.length(1);
+    expect(wrapper.dive().find(QueryAndSaveBtns)).toHaveLength(1);
   });
 
   it('renders ControlPanelsContainer', () => {
-    expect(wrapper.dive().find(ControlPanelsContainer)).to.have.length(1);
+    expect(wrapper.dive().find(ControlPanelsContainer)).toHaveLength(1);
   });
 
   it('renders ChartContainer', () => {
-    expect(wrapper.dive().find(ChartContainer)).to.have.length(1);
+    expect(wrapper.dive().find(ChartContainer)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx
index 62e4fe3..909757d 100644
--- a/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import FilterDefinitionOption from '../../../../src/explore/components/FilterDefinitionOption';
@@ -18,18 +17,18 @@ const sumValueAdhocMetric = new AdhocMetric({
 describe('FilterDefinitionOption', () => {
   it('renders a ColumnOption given a column', () => {
     const wrapper = shallow(<FilterDefinitionOption option={{ column_name: 'a_column' }} />);
-    expect(wrapper.find(ColumnOption)).to.have.lengthOf(1);
+    expect(wrapper.find(ColumnOption)).toHaveLength(1);
   });
 
   it('renders a AdhocMetricStaticOption given an adhoc metric', () => {
     const wrapper = shallow(<FilterDefinitionOption option={sumValueAdhocMetric} />);
-    expect(wrapper.find(AdhocMetricStaticOption)).to.have.lengthOf(1);
+    expect(wrapper.find(AdhocMetricStaticOption)).toHaveLength(1);
   });
 
   it('renders the metric name given a saved metric', () => {
     const wrapper = shallow((
       <FilterDefinitionOption option={{ saved_metric_name: 'my_custom_metric' }} />
     ));
-    expect(wrapper.text()).to.equal('<ColumnTypeLabel />my_custom_metric');
+    expect(wrapper.text()).toBe('<ColumnTypeLabel />my_custom_metric');
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx
index 4774d9c..e191c41 100644
--- a/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { OverlayTrigger } from 'react-bootstrap';
 
@@ -26,13 +25,13 @@ describe('FixedOrMetricControl', () => {
 
   it('renders a OverlayTrigger', () => {
     const controlHeader = wrapper.find(ControlHeader);
-    expect(controlHeader).to.have.lengthOf(1);
-    expect(wrapper.find(OverlayTrigger)).to.have.length(1);
+    expect(controlHeader).toHaveLength(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
   });
 
   it('renders a TextControl and a SelectControl', () => {
     const popOver = shallow(inst.renderPopover());
-    expect(popOver.find(TextControl)).to.have.lengthOf(1);
-    expect(popOver.find(SelectControl)).to.have.lengthOf(1);
+    expect(popOver.find(TextControl)).toHaveLength(1);
+    expect(popOver.find(SelectControl)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx
index 1a8f766..989b382 100644
--- a/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import configureStore from 'redux-mock-store';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import MetricDefinitionOption from '../../../../src/explore/components/MetricDefinitionOption';
@@ -18,16 +17,16 @@ describe('MetricDefinitionOption', () => {
 
   it('renders a MetricOption given a saved metric', () => {
     const wrapper = setup({ option: { metric_name: 'a_saved_metric' } });
-    expect(wrapper.find(MetricOption)).to.have.lengthOf(1);
+    expect(wrapper.find(MetricOption)).toHaveLength(1);
   });
 
   it('renders a ColumnOption given a column', () => {
     const wrapper = setup({ option: { column_name: 'a_column' } });
-    expect(wrapper.find(ColumnOption)).to.have.lengthOf(1);
+    expect(wrapper.find(ColumnOption)).toHaveLength(1);
   });
 
   it('renders an AggregateOption given an aggregate metric', () => {
     const wrapper = setup({ option: { aggregate_name: 'an_aggregate' } });
-    expect(wrapper.find(AggregateOption)).to.have.lengthOf(1);
+    expect(wrapper.find(AggregateOption)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx b/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx
index 5e0f3ae..8d568ef 100644
--- a/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import MetricDefinitionValue from '../../../../src/explore/components/MetricDefinitionValue';
@@ -17,13 +16,13 @@ const sumValueAdhocMetric = new AdhocMetric({
 describe('MetricDefinitionValue', () => {
   it('renders a MetricOption given a saved metric', () => {
     const wrapper = shallow(<MetricDefinitionValue option={{ metric_name: 'a_saved_metric' }} />);
-    expect(wrapper.find(MetricOption)).to.have.lengthOf(1);
+    expect(wrapper.find(MetricOption)).toHaveLength(1);
   });
 
   it('renders an AdhocMetricOption given an adhoc metric', () => {
     const wrapper = shallow((
       <MetricDefinitionValue onMetricEdit={() => {}} option={sumValueAdhocMetric} />
     ));
-    expect(wrapper.find(AdhocMetricOption)).to.have.lengthOf(1);
+    expect(wrapper.find(AdhocMetricOption)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx
index 6685bff..8a91262 100644
--- a/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import MetricsControl from '../../../../src/explore/components/controls/MetricsControl';
@@ -49,14 +48,14 @@ describe('MetricsControl', () => {
 
   it('renders an OnPasteSelect', () => {
     const { wrapper } = setup();
-    expect(wrapper.find(OnPasteSelect)).to.have.lengthOf(1);
+    expect(wrapper.find(OnPasteSelect)).toHaveLength(1);
   });
 
   describe('constructor', () => {
 
     it('unifies options for the dropdown select with aggregates', () => {
       const { wrapper } = setup();
-      expect(wrapper.state('options')).to.deep.equal([
+      expect(wrapper.state('options')).toEqual([
         { optionName: '_col_source', type: 'VARCHAR(255)', column_name: 'source' },
         { optionName: '_col_target', type: 'VARCHAR(255)', column_name: 'target' },
         { optionName: '_col_value', type: 'DOUBLE', column_name: 'value' },
@@ -83,9 +82,9 @@ describe('MetricsControl', () => {
       });
 
       const adhocMetric = wrapper.state('value')[0];
-      expect(adhocMetric instanceof AdhocMetric).to.be.true;
-      expect(adhocMetric.optionName.length).to.be.above(10);
-      expect(wrapper.state('value')).to.deep.equal([
+      expect(adhocMetric instanceof AdhocMetric).toBe(true);
+      expect(adhocMetric.optionName.length).toBeGreaterThan(10);
+      expect(wrapper.state('value')).toEqual([
         {
           expressionType: EXPRESSION_TYPES.SIMPLE,
           column: { type: 'double', column_name: 'value' },
@@ -108,7 +107,7 @@ describe('MetricsControl', () => {
       const { wrapper, onChange } = setup();
       const select = wrapper.find(OnPasteSelect);
       select.simulate('change', [{ metric_name: 'sum__value' }]);
-      expect(onChange.lastCall.args).to.deep.equal([['sum__value']]);
+      expect(onChange.lastCall.args).toEqual([['sum__value']]);
     });
 
     it('handles columns being selected', () => {
@@ -117,8 +116,8 @@ describe('MetricsControl', () => {
       select.simulate('change', [valueColumn]);
 
       const adhocMetric = onChange.lastCall.args[0][0];
-      expect(adhocMetric instanceof AdhocMetric).to.be.true;
-      expect(onChange.lastCall.args).to.deep.equal([[{
+      expect(adhocMetric instanceof AdhocMetric).toBe(true);
+      expect(onChange.lastCall.args).toEqual([[{
         expressionType: EXPRESSION_TYPES.SIMPLE,
         column: valueColumn,
         aggregate: AGGREGATES.SUM,
@@ -145,16 +144,16 @@ describe('MetricsControl', () => {
 
       select.simulate('change', [{ aggregate_name: 'SUM', optionName: 'SUM' }]);
 
-      expect(setInputSpy.calledWith('SUM()')).to.be.true;
-      expect(handleInputSpy.calledWith({ target: { value: 'SUM()' } })).to.be.true;
-      expect(onChange.lastCall.args).to.deep.equal([[]]);
+      expect(setInputSpy.calledWith('SUM()')).toBe(true);
+      expect(handleInputSpy.calledWith({ target: { value: 'SUM()' } })).toBe(true);
+      expect(onChange.lastCall.args).toEqual([[]]);
     });
 
     it('preserves existing selected AdhocMetrics', () => {
       const { wrapper, onChange } = setup();
       const select = wrapper.find(OnPasteSelect);
       select.simulate('change', [{ metric_name: 'sum__value' }, sumValueAdhocMetric]);
-      expect(onChange.lastCall.args).to.deep.equal([['sum__value', sumValueAdhocMetric]]);
+      expect(onChange.lastCall.args).toEqual([['sum__value', sumValueAdhocMetric]]);
     });
   });
 
@@ -167,7 +166,7 @@ describe('MetricsControl', () => {
       const editedMetric = sumValueAdhocMetric.duplicateWith({ aggregate: AGGREGATES.AVG });
       wrapper.instance().onMetricEdit(editedMetric);
 
-      expect(onChange.lastCall.args).to.deep.equal([[
+      expect(onChange.lastCall.args).toEqual([[
         editedMetric,
       ]]);
     });
@@ -177,17 +176,17 @@ describe('MetricsControl', () => {
     it('handles an aggregate in the input', () => {
       const { wrapper } = setup();
 
-      expect(wrapper.state('aggregateInInput')).to.be.null;
+      expect(wrapper.state('aggregateInInput')).toBeNull();
       wrapper.instance().checkIfAggregateInInput('AVG(');
-      expect(wrapper.state('aggregateInInput')).to.equal(AGGREGATES.AVG);
+      expect(wrapper.state('aggregateInInput')).toBe(AGGREGATES.AVG);
     });
 
     it('handles no aggregate in the input', () => {
       const { wrapper } = setup();
 
-      expect(wrapper.state('aggregateInInput')).to.be.null;
+      expect(wrapper.state('aggregateInInput')).toBeNull();
       wrapper.instance().checkIfAggregateInInput('colu');
-      expect(wrapper.state('aggregateInInput')).to.be.null;
+      expect(wrapper.state('aggregateInInput')).toBeNull();
     });
   });
 
@@ -202,7 +201,7 @@ describe('MetricsControl', () => {
           expression: 'SUM(FANCY(metric))',
         },
         'a',
-      )).to.be.true;
+      )).toBe(true);
     });
 
     it('includes auto generated avg metrics for druid', () => {
@@ -215,7 +214,7 @@ describe('MetricsControl', () => {
           expression: 'AVG(metric)',
         },
         'a',
-      )).to.be.true;
+      )).toBe(true);
     });
 
     it('includes columns and aggregates', () => {
@@ -224,12 +223,12 @@ describe('MetricsControl', () => {
       expect(!!wrapper.instance().selectFilterOption(
         { type: 'VARCHAR(255)', column_name: 'source', optionName: '_col_source' },
         'sou',
-      )).to.be.true;
+      )).toBe(true);
 
       expect(!!wrapper.instance().selectFilterOption(
         { aggregate_name: 'AVG', optionName: '_aggregate_AVG' },
         'av',
-      )).to.be.true;
+      )).toBe(true);
     });
 
     it('includes columns based on verbose_name', () => {
@@ -238,7 +237,7 @@ describe('MetricsControl', () => {
       expect(!!wrapper.instance().selectFilterOption(
         { metric_name: 'sum__num', verbose_name: 'babies', optionName: '_col_sum_num' },
         'bab',
-      )).to.be.true;
+      )).toBe(true);
     });
 
     it('excludes auto generated avg metrics for sqla', () => {
@@ -251,7 +250,7 @@ describe('MetricsControl', () => {
           expression: 'AVG(metric)',
         },
         'a',
-      )).to.be.false;
+      )).toBe(false);
     });
 
     it('includes custom made simple saved metrics', () => {
@@ -264,7 +263,7 @@ describe('MetricsControl', () => {
           expression: 'SUM(value)',
         },
         'sum',
-      )).to.be.true;
+      )).toBe(true);
     });
 
     it('excludes auto generated metrics', () => {
@@ -277,7 +276,7 @@ describe('MetricsControl', () => {
           expression: 'SUM(value)',
         },
         'sum',
-      )).to.be.false;
+      )).toBe(false);
 
       expect(!!wrapper.instance().selectFilterOption(
         {
@@ -286,7 +285,7 @@ describe('MetricsControl', () => {
           expression: 'SUM("table"."value")',
         },
         'sum',
-      )).to.be.false;
+      )).toBe(false);
     });
 
     it('filters out metrics if the input begins with an aggregate', () => {
@@ -296,7 +295,7 @@ describe('MetricsControl', () => {
       expect(!!wrapper.instance().selectFilterOption(
         { metric_name: 'metric', expression: 'SUM(FANCY(metric))' },
         'SUM(',
-      )).to.be.false;
+      )).toBe(false);
     });
 
     it('includes columns if the input begins with an aggregate', () => {
@@ -306,7 +305,7 @@ describe('MetricsControl', () => {
       expect(!!wrapper.instance().selectFilterOption(
         { type: 'DOUBLE', column_name: 'value' },
         'SUM(',
-      )).to.be.true;
+      )).toBe(true);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx b/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx
index 70712af..debaf5d 100644
--- a/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import sinon from 'sinon';
 
@@ -14,7 +13,7 @@ describe('QueryAndSaveButtons', () => {
 
   // It must render
   it('renders', () => {
-    expect(React.isValidElement(<QueryAndSaveButtons {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<QueryAndSaveButtons {...defaultProps} />)).toBe(true);
   });
 
   // Test the output
@@ -26,18 +25,18 @@ describe('QueryAndSaveButtons', () => {
     });
 
     it('renders 2 buttons', () => {
-      expect(wrapper.find(Button)).to.have.lengthOf(2);
+      expect(wrapper.find(Button)).toHaveLength(2);
     });
 
     it('renders buttons with correct text', () => {
-      expect(wrapper.find(Button).contains(' Run Query')).to.eql(true);
-      expect(wrapper.find(Button).contains(' Save')).to.eql(true);
+      expect(wrapper.find(Button).contains(' Run Query')).toBe(true);
+      expect(wrapper.find(Button).contains(' Save')).toBe(true);
     });
 
     it('calls onQuery when query button is clicked', () => {
       const queryButton = wrapper.find('.query');
       queryButton.simulate('click');
-      expect(defaultProps.onQuery.called).to.eql(true);
+      expect(defaultProps.onQuery.called).toBe(true);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx b/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx
index 53dd860..761f2e1 100644
--- a/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Label } from 'react-bootstrap';
 
@@ -14,12 +13,12 @@ describe('RowCountLabel', () => {
   };
 
   it('is valid', () => {
-    expect(React.isValidElement(<RowCountLabel {...defaultProps} />)).to.equal(true);
+    expect(React.isValidElement(<RowCountLabel {...defaultProps} />)).toBe(true);
   });
   it('renders a Label and a TooltipWrapper', () => {
     const wrapper = shallow(<RowCountLabel {...defaultProps} />);
-    expect(wrapper.find(Label)).to.have.lengthOf(1);
-    expect(wrapper.find(TooltipWrapper)).to.have.lengthOf(1);
+    expect(wrapper.find(Label)).toHaveLength(1);
+    expect(wrapper.find(TooltipWrapper)).toHaveLength(1);
   });
   it('renders a warning when limit is reached', () => {
     const props = {
@@ -27,6 +26,6 @@ describe('RowCountLabel', () => {
       limit: 100,
     };
     const wrapper = shallow(<RowCountLabel {...props} />);
-    expect(wrapper.find(Label).first().props().bsStyle).to.equal('warning');
+    expect(wrapper.find(Label).first().props().bsStyle).toBe('warning');
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx
index f29f491..9df2c05 100644
--- a/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import RunQueryActionButton
@@ -25,10 +24,10 @@ describe('RunQueryActionButton', () => {
   it('is a valid react element', () => {
     expect(
       React.isValidElement(<RunQueryActionButton {...defaultProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('renders a single Button', () => {
-    expect(wrapper.find(Button)).to.have.lengthOf(1);
+    expect(wrapper.find(Button)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx b/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx
index 4b4f1a4..6b7c9ae 100644
--- a/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx
@@ -2,7 +2,6 @@ import React from 'react';
 import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
 
-import { expect } from 'chai';
 import { shallow, mount } from 'enzyme';
 import { Modal, Button, Radio } from 'react-bootstrap';
 import sinon from 'sinon';
@@ -51,25 +50,25 @@ describe('SaveModal', () => {
 
   it('renders a Modal with 7 inputs and 2 buttons', () => {
     const wrapper = getWrapper();
-    expect(wrapper.find(Modal)).to.have.lengthOf(1);
-    expect(wrapper.find('input')).to.have.lengthOf(2);
-    expect(wrapper.find(Button)).to.have.lengthOf(2);
-    expect(wrapper.find(Radio)).to.have.lengthOf(5);
+    expect(wrapper.find(Modal)).toHaveLength(1);
+    expect(wrapper.find('input')).toHaveLength(2);
+    expect(wrapper.find(Button)).toHaveLength(2);
+    expect(wrapper.find(Radio)).toHaveLength(5);
   });
 
   it('does not show overwrite option for new slice', () => {
     const wrapperNewSlice = getWrapper();
     wrapperNewSlice.setProps({ slice: null });
-    expect(wrapperNewSlice.find('#overwrite-radio')).to.have.lengthOf(0);
-    expect(wrapperNewSlice.find('#saveas-radio')).to.have.lengthOf(1);
+    expect(wrapperNewSlice.find('#overwrite-radio')).toHaveLength(0);
+    expect(wrapperNewSlice.find('#saveas-radio')).toHaveLength(1);
   });
 
   it('disable overwrite option for non-owner', () => {
     const wrapperForNonOwner = getWrapper();
     wrapperForNonOwner.setProps({ can_overwrite: false });
     const overwriteRadio = wrapperForNonOwner.find('#overwrite-radio');
-    expect(overwriteRadio).to.have.lengthOf(1);
-    expect(overwriteRadio.prop('disabled')).to.equal(true);
+    expect(overwriteRadio).toHaveLength(1);
+    expect(overwriteRadio.prop('disabled')).toBe(true);
   });
 
   it('saves a new slice', () => {
@@ -78,14 +77,14 @@ describe('SaveModal', () => {
     wrapperForNewSlice.instance().changeAction('saveas');
     const saveasRadio = wrapperForNewSlice.find('#saveas-radio');
     saveasRadio.simulate('click');
-    expect(wrapperForNewSlice.state().action).to.equal('saveas');
+    expect(wrapperForNewSlice.state().action).toBe('saveas');
   });
 
   it('overwrite a slice', () => {
     const wrapperForOverwrite = getWrapper();
     const overwriteRadio = wrapperForOverwrite.find('#overwrite-radio');
     overwriteRadio.simulate('click');
-    expect(wrapperForOverwrite.state().action).to.equal('overwrite');
+    expect(wrapperForOverwrite.state().action).toBe('overwrite');
   });
 
   it('componentDidMount', () => {
@@ -94,8 +93,8 @@ describe('SaveModal', () => {
     mount(<SaveModal {...defaultProps} />, {
       context: { store },
     });
-    expect(SaveModal.prototype.componentDidMount.calledOnce).to.equal(true);
-    expect(saveModalActions.fetchDashboards.calledOnce).to.equal(true);
+    expect(SaveModal.prototype.componentDidMount.calledOnce).toBe(true);
+    expect(saveModalActions.fetchDashboards.calledOnce).toBe(true);
 
     SaveModal.prototype.componentDidMount.restore();
     saveModalActions.fetchDashboards.restore();
@@ -105,13 +104,13 @@ describe('SaveModal', () => {
     const wrapper = getWrapper();
 
     wrapper.instance().onChange('newSliceName', mockEvent);
-    expect(wrapper.state().newSliceName).to.equal(mockEvent.target.value);
+    expect(wrapper.state().newSliceName).toBe(mockEvent.target.value);
 
     wrapper.instance().onChange('saveToDashboardId', mockEvent);
-    expect(wrapper.state().saveToDashboardId).to.equal(mockEvent.value);
+    expect(wrapper.state().saveToDashboardId).toBe(mockEvent.value);
 
     wrapper.instance().onChange('newDashboardName', mockEvent);
-    expect(wrapper.state().newDashboardName).to.equal(mockEvent.target.value);
+    expect(wrapper.state().newDashboardName).toBe(mockEvent.target.value);
   });
 
   describe('saveOrOverwrite', () => {
@@ -132,7 +131,7 @@ describe('SaveModal', () => {
       const wrapper = getWrapper();
       wrapper.instance().saveOrOverwrite(true);
       const args = saveModalActions.saveSlice.getCall(0).args;
-      expect(args[0]).to.deep.equal(defaultProps.form_data);
+      expect(args[0]).toEqual(defaultProps.form_data);
     });
     it('existing dashboard', () => {
       const wrapper = getWrapper();
@@ -140,12 +139,12 @@ describe('SaveModal', () => {
 
       wrapper.setState({ addToDash: 'existing' });
       wrapper.instance().saveOrOverwrite(true);
-      expect(wrapper.state().alert).to.equal('Please select a dashboard');
+      expect(wrapper.state().alert).toBe('Please select a dashboard');
 
       wrapper.setState({ saveToDashboardId });
       wrapper.instance().saveOrOverwrite(true);
       const args = saveModalActions.saveSlice.getCall(0).args;
-      expect(args[1].save_to_dashboard_id).to.equal(saveToDashboardId);
+      expect(args[1].save_to_dashboard_id).toBe(saveToDashboardId);
     });
     it('new dashboard', () => {
       const wrapper = getWrapper();
@@ -153,12 +152,12 @@ describe('SaveModal', () => {
 
       wrapper.setState({ addToDash: 'new' });
       wrapper.instance().saveOrOverwrite(true);
-      expect(wrapper.state().alert).to.equal('Please enter a dashboard name');
+      expect(wrapper.state().alert).toBe('Please enter a dashboard name');
 
       wrapper.setState({ newDashboardName });
       wrapper.instance().saveOrOverwrite(true);
       const args = saveModalActions.saveSlice.getCall(0).args;
-      expect(args[1].new_dashboard_name).to.equal(newDashboardName);
+      expect(args[1].new_dashboard_name).toBe(newDashboardName);
     });
   });
 
@@ -187,28 +186,27 @@ describe('SaveModal', () => {
 
     it('makes the ajax request', () => {
       makeRequest();
-      expect(ajaxStub.callCount).to.equal(1);
+      expect(ajaxStub.callCount).toBe(1);
     });
 
     it('calls correct url', () => {
       const url = '/dashboardasync/api/read?_flt_0_owners=' + userID;
       makeRequest();
-      expect(ajaxStub.getCall(0).args[0].url).to.be.equal(url);
+      expect(ajaxStub.getCall(0).args[0].url).toBe(url);
     });
 
     it('calls correct actions on error', () => {
       ajaxStub.yieldsTo('error', { responseJSON: { error: 'error text' } });
       makeRequest();
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0].type).to.equal(saveModalActions.FETCH_DASHBOARDS_FAILED);
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0].type).toBe(saveModalActions.FETCH_DASHBOARDS_FAILED);
     });
 
     it('calls correct actions on success', () => {
       ajaxStub.yieldsTo('success', mockDashboardData);
       makeRequest();
-      expect(dispatch.callCount).to.equal(1);
-      expect(dispatch.getCall(0).args[0].type)
-        .to.equal(saveModalActions.FETCH_DASHBOARDS_SUCCEEDED);
+      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.getCall(0).args[0].type).toBe(saveModalActions.FETCH_DASHBOARDS_SUCCEEDED);
     });
   });
 
@@ -218,8 +216,8 @@ describe('SaveModal', () => {
     wrapper.setProps({ alert: 'old alert' });
 
     wrapper.instance().removeAlert();
-    expect(saveModalActions.removeSaveModalAlert.callCount).to.equal(1);
-    expect(wrapper.state().alert).to.be.a('null');
+    expect(saveModalActions.removeSaveModalAlert.callCount).toBe(1);
+    expect(wrapper.state().alert).toBeNull();
     saveModalActions.removeSaveModalAlert.restore();
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx
index b0cf1db..df2aff3 100644
--- a/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx
@@ -3,7 +3,6 @@ import React from 'react';
 import Select, { Creatable } from 'react-select';
 import VirtualizedSelect from 'react-virtualized-select';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import OnPasteSelect from '../../../../src/components/OnPasteSelect';
 import VirtualizedRendererWrap from '../../../../src/components/VirtualizedRendererWrap';
@@ -29,43 +28,43 @@ describe('SelectControl', () => {
   });
 
   it('renders an OnPasteSelect', () => {
-    expect(wrapper.find(OnPasteSelect)).to.have.lengthOf(1);
+    expect(wrapper.find(OnPasteSelect)).toHaveLength(1);
   });
 
   it('calls onChange when toggled', () => {
     const select = wrapper.find(OnPasteSelect);
     select.simulate('change', { value: 50 });
-    expect(defaultProps.onChange.calledWith(50)).to.be.true;
+    expect(defaultProps.onChange.calledWith(50)).toBe(true);
   });
 
   it('passes VirtualizedSelect as selectWrap', () => {
     const select = wrapper.find(OnPasteSelect);
-    expect(select.props().selectWrap).to.equal(VirtualizedSelect);
+    expect(select.props().selectWrap).toBe(VirtualizedSelect);
   });
 
   it('passes Creatable as selectComponent when freeForm=true', () => {
     wrapper = shallow(<SelectControl {...defaultProps} freeForm />);
     const select = wrapper.find(OnPasteSelect);
-    expect(select.props().selectComponent).to.equal(Creatable);
+    expect(select.props().selectComponent).toBe(Creatable);
   });
 
   it('passes Select as selectComponent when freeForm=false', () => {
     const select = wrapper.find(OnPasteSelect);
-    expect(select.props().selectComponent).to.equal(Select);
+    expect(select.props().selectComponent).toBe(Select);
   });
 
   it('wraps optionRenderer in a VirtualizedRendererWrap', () => {
     const select = wrapper.find(OnPasteSelect);
     const defaultOptionRenderer = SelectControl.defaultProps.optionRenderer;
     const wrappedRenderer = VirtualizedRendererWrap(defaultOptionRenderer);
-    expect(select.props().optionRenderer).to.be.a('Function');
+    expect(typeof select.props().optionRenderer).toBe('function');
     // different instances of wrapper with same inner renderer are unequal
-    expect(select.props().optionRenderer.name).to.equal(wrappedRenderer.name);
+    expect(select.props().optionRenderer.name).toBe(wrappedRenderer.name);
   });
 
   describe('getOptions', () => {
     it('returns the correct options', () => {
-      expect(wrapper.instance().getOptions(defaultProps)).to.deep.equal(options);
+      expect(wrapper.instance().getOptions(defaultProps)).toEqual(options);
     });
 
     it('returns the correct options when freeform is set to true', () => {
@@ -78,7 +77,7 @@ describe('SelectControl', () => {
         { value: 'one', label: 'one' },
         { value: 'two', label: 'two' },
       ];
-      expect(wrapper.instance().getOptions(freeFormProps)).to.deep.equal(newOptions);
+      expect(wrapper.instance().getOptions(freeFormProps)).toEqual(newOptions);
     });
   });
 
@@ -95,7 +94,7 @@ describe('SelectControl', () => {
         value: null,
       };
       wrapper.setProps(newProps);
-      expect(wrapper.state().options).to.deep.equal(updatedOptions);
+      expect(wrapper.state().options).toEqual(updatedOptions);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx b/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx
index d6fc122..c611c82 100644
--- a/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx
@@ -2,7 +2,6 @@
 import React from 'react';
 import { FormControl } from 'react-bootstrap';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import AceEditor from 'react-ace';
 
@@ -21,20 +20,20 @@ describe('SelectControl', () => {
   });
 
   it('renders a FormControl', () => {
-    expect(wrapper.find(FormControl)).to.have.lengthOf(1);
+    expect(wrapper.find(FormControl)).toHaveLength(1);
   });
 
   it('calls onChange when toggled', () => {
     const select = wrapper.find(FormControl);
     select.simulate('change', { target: { value: 'x' } });
-    expect(defaultProps.onChange.calledWith('x')).to.be.true;
+    expect(defaultProps.onChange.calledWith('x')).toBe(true);
   });
 
   it('renders a AceEditor when language is specified', () => {
     const props = Object.assign({}, defaultProps);
     props.language = 'markdown';
     wrapper = shallow(<TextAreaControl {...props} />);
-    expect(wrapper.find(FormControl)).to.have.lengthOf(0);
-    expect(wrapper.find(AceEditor)).to.have.lengthOf(1);
+    expect(wrapper.find(FormControl)).toHaveLength(0);
+    expect(wrapper.find(AceEditor)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx
index 3a03d76..9735702 100644
--- a/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx
@@ -2,7 +2,6 @@
 import React from 'react';
 import { FormControl, OverlayTrigger } from 'react-bootstrap';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import TimeSeriesColumnControl from '../../../../src/explore/components/controls/TimeSeriesColumnControl';
@@ -22,11 +21,11 @@ describe('SelectControl', () => {
   });
 
   it('renders an OverlayTrigger', () => {
-    expect(wrapper.find(OverlayTrigger)).to.have.lengthOf(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
   });
 
   it('renders an Popover', () => {
     const popOver = shallow(inst.renderPopover());
-    expect(popOver.find(FormControl)).to.have.lengthOf(3);
+    expect(popOver.find(FormControl)).toHaveLength(3);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx
index eef3f27..8e5d92f 100644
--- a/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx
@@ -1,6 +1,5 @@
 /* eslint-disable no-unused-expressions */
 import React from 'react';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { OverlayTrigger, Label } from 'react-bootstrap';
 
@@ -30,16 +29,16 @@ describe('ViewportControl', () => {
 
   it('renders a OverlayTrigger', () => {
     const controlHeader = wrapper.find(ControlHeader);
-    expect(controlHeader).to.have.lengthOf(1);
-    expect(wrapper.find(OverlayTrigger)).to.have.length(1);
+    expect(controlHeader).toHaveLength(1);
+    expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
   });
 
   it('renders a Popover with 5 TextControl', () => {
     const popOver = shallow(inst.renderPopover());
-    expect(popOver.find(TextControl)).to.have.lengthOf(5);
+    expect(popOver.find(TextControl)).toHaveLength(5);
   });
 
   it('renders a summary in the label', () => {
-    expect(wrapper.find(Label).first().render().text()).to.equal('6° 51\' 8.50" | 31° 13\' 21.56"');
+    expect(wrapper.find(Label).first().render().text()).toBe('6° 51\' 8.50" | 31° 13\' 21.56"');
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx
index bd41e1d..d3f6fd4 100644
--- a/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import { Modal } from 'react-bootstrap';
 import VizTypeControl from '../../../../src/explore/components/controls/VizTypeControl';
@@ -20,17 +19,17 @@ describe('VizTypeControl', () => {
   });
 
   it('renders a Modal', () => {
-    expect(wrapper.find(Modal)).to.have.lengthOf(1);
+    expect(wrapper.find(Modal)).toHaveLength(1);
   });
 
   it('calls onChange when toggled', () => {
     const select = wrapper.find('.viztype-selector-container').first();
     select.simulate('click');
-    expect(defaultProps.onChange.called).to.equal(true);
+    expect(defaultProps.onChange.called).toBe(true);
   });
   it('filters images based on text input', () => {
-    expect(wrapper.find('img').length).to.be.above(20);
+    expect(wrapper.find('img').length).toBeGreaterThan(20);
     wrapper.setState({ filter: 'time' });
-    expect(wrapper.find('img').length).to.be.below(10);
+    expect(wrapper.find('img').length).toBeLessThan(10);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/exploreActions_spec.js b/superset/assets/spec/javascripts/explore/exploreActions_spec.js
index 7b4749a..97c0ec8 100644
--- a/superset/assets/spec/javascripts/explore/exploreActions_spec.js
+++ b/superset/assets/spec/javascripts/explore/exploreActions_spec.js
@@ -1,5 +1,4 @@
 /* eslint-disable no-unused-expressions */
-import { expect } from 'chai';
 import { defaultState } from '../../../src/explore/store';
 import exploreReducer from '../../../src/explore/reducers/exploreReducer';
 import * as actions from '../../../src/explore/actions/exploreActions';
@@ -8,11 +7,11 @@ describe('reducers', () => {
   it('sets correct control value given a key and value', () => {
     const newState = exploreReducer(
       defaultState, actions.setControlValue('x_axis_label', 'x', []));
-    expect(newState.controls.x_axis_label.value).to.equal('x');
+    expect(newState.controls.x_axis_label.value).toBe('x');
   });
   it('setControlValue works as expected with a checkbox', () => {
     const newState = exploreReducer(defaultState,
       actions.setControlValue('show_legend', true, []));
-    expect(newState.controls.show_legend.value).to.equal(true);
+    expect(newState.controls.show_legend.value).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/utils_spec.jsx b/superset/assets/spec/javascripts/explore/utils_spec.jsx
index 9d2eaf0..633f7ce 100644
--- a/superset/assets/spec/javascripts/explore/utils_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/utils_spec.jsx
@@ -1,4 +1,3 @@
-import { expect } from 'chai';
 import URI from 'urijs';
 import { getExploreUrlAndPayload, getExploreLongUrl } from '../../../src/explore/exploreUtils';
 
@@ -9,14 +8,14 @@ describe('exploreUtils', () => {
   };
   const sFormData = JSON.stringify(formData);
   function compareURI(uri1, uri2) {
-    expect(uri1.toString()).to.equal(uri2.toString());
+    expect(uri1.toString()).toBe(uri2.toString());
   }
 
   describe('getExploreUrlAndPayload', () => {
     it('generates proper base url', () => {
       // This assertion is to show clearly the value of location.href
       // in the context of unit tests.
-      expect(location.href).to.equal('about:blank');
+      expect(location.href).toBe('http://localhost/');
 
       const { url, payload } = getExploreUrlAndPayload({
         formData,
@@ -28,7 +27,7 @@ describe('exploreUtils', () => {
         URI(url),
         URI('/superset/explore/'),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
     it('generates proper json url', () => {
       const { url, payload } = getExploreUrlAndPayload({
@@ -41,7 +40,7 @@ describe('exploreUtils', () => {
         URI(url),
         URI('/superset/explore_json/'),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
     it('generates proper json forced url', () => {
       const { url, payload } = getExploreUrlAndPayload({
@@ -55,7 +54,7 @@ describe('exploreUtils', () => {
         URI('/superset/explore_json/')
           .search({ force: 'true' }),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
     it('generates proper csv URL', () => {
       const { url, payload } = getExploreUrlAndPayload({
@@ -69,7 +68,7 @@ describe('exploreUtils', () => {
         URI('/superset/explore_json/')
           .search({ csv: 'true' }),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
     it('generates proper standalone URL', () => {
       const { url, payload } = getExploreUrlAndPayload({
@@ -83,7 +82,7 @@ describe('exploreUtils', () => {
         URI('/superset/explore/')
           .search({ standalone: 'true' }),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
     it('preserves main URLs params', () => {
       const { url, payload } = getExploreUrlAndPayload({
@@ -97,7 +96,7 @@ describe('exploreUtils', () => {
         URI('/superset/explore_json/')
           .search({ foo: 'bar' }),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
     it('generate proper save slice url', () => {
       const { url, payload } = getExploreUrlAndPayload({
@@ -111,7 +110,7 @@ describe('exploreUtils', () => {
         URI('/superset/explore_json/')
           .search({ foo: 'bar' }),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
     it('generate proper saveas slice url', () => {
       const { url, payload } = getExploreUrlAndPayload({
@@ -125,7 +124,7 @@ describe('exploreUtils', () => {
         URI('/superset/explore_json/')
           .search({ foo: 'bar' }),
       );
-      expect(payload).to.deep.equals(formData);
+      expect(payload).toEqual(formData);
     });
   });
 
diff --git a/superset/assets/spec/javascripts/logger_spec.js b/superset/assets/spec/javascripts/logger_spec.js
index e5b46b4..4b85759 100644
--- a/superset/assets/spec/javascripts/logger_spec.js
+++ b/superset/assets/spec/javascripts/logger_spec.js
@@ -1,5 +1,4 @@
 import $ from 'jquery';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import { Logger, ActionLog } from '../../src/logger';
@@ -7,40 +6,43 @@ import { Logger, ActionLog } from '../../src/logger';
 describe('ActionLog', () => {
   it('should be a constructor', () => {
     const newLogger = new ActionLog({});
-    expect(newLogger instanceof ActionLog).to.equal(true);
+    expect(newLogger instanceof ActionLog).toBe(true);
   });
 
-  it('should set the eventNames, impressionId, source, sourceId, and sendNow init parameters', () => {
-    const eventNames = [];
-    const impressionId = 'impressionId';
-    const source = 'source';
-    const sourceId = 'sourceId';
-    const sendNow = true;
-
-    const log = new ActionLog({ eventNames, impressionId, source, sourceId, sendNow });
-    expect(log.eventNames).to.equal(eventNames);
-    expect(log.impressionId).to.equal(impressionId);
-    expect(log.source).to.equal(source);
-    expect(log.sourceId).to.equal(sourceId);
-    expect(log.sendNow).to.equal(sendNow);
-  });
+  it(
+    'should set the eventNames, impressionId, source, sourceId, and sendNow init parameters',
+    () => {
+      const eventNames = [];
+      const impressionId = 'impressionId';
+      const source = 'source';
+      const sourceId = 'sourceId';
+      const sendNow = true;
+
+      const log = new ActionLog({ eventNames, impressionId, source, sourceId, sendNow });
+      expect(log.eventNames).toBe(eventNames);
+      expect(log.impressionId).toBe(impressionId);
+      expect(log.source).toBe(source);
+      expect(log.sourceId).toBe(sourceId);
+      expect(log.sendNow).toBe(sendNow);
+    },
+  );
 
   it('should set attributes with the setAttribute method', () => {
     const log = new ActionLog({});
-    expect(log.test).to.equal(undefined);
+    expect(log.test).toBeUndefined();
     log.setAttribute('test', 'testValue');
-    expect(log.test).to.equal('testValue');
+    expect(log.test).toBe('testValue');
   });
 
   it('should track added events', () => {
     const log = new ActionLog({});
     const eventName = 'myEventName';
     const eventBody = { test: 'event' };
-    expect(log.events[eventName]).to.equal(undefined);
+    expect(log.events[eventName]).toBeUndefined();
 
     log.addEvent(eventName, eventBody);
-    expect(log.events[eventName]).to.have.length(1);
-    expect(log.events[eventName][0]).to.deep.include(eventBody);
+    expect(log.events[eventName]).toHaveLength(1);
+    expect(log.events[eventName][0]).toMatchObject(eventBody);
   });
 });
 
@@ -51,8 +53,8 @@ describe('Logger', () => {
     const log = new ActionLog({ eventNames: [eventName] });
     Logger.start(log);
     Logger.append(eventName, eventBody);
-    expect(log.events[eventName]).to.have.length(1);
-    expect(log.events[eventName][0]).to.deep.include(eventBody);
+    expect(log.events[eventName]).toHaveLength(1);
+    expect(log.events[eventName][0]).toMatchObject(eventBody);
     Logger.end(log);
   });
 
@@ -75,12 +77,12 @@ describe('Logger', () => {
       const log = setup();
       Logger.start(log);
       Logger.append(eventNames[0], { test: 'event' });
-      expect(log.events[eventNames[0]]).to.have.length(1);
+      expect(log.events[eventNames[0]]).toHaveLength(1);
       Logger.end(log);
-      expect($.ajax.calledOnce).to.equal(true);
+      expect($.ajax.calledOnce).toBe(true);
       const args = $.ajax.getCall(0).args[0];
-      expect(args.url).to.equal('/superset/log/');
-      expect(args.method).to.equal('POST');
+      expect(args.url).toBe('/superset/log/');
+      expect(args.method).toBe('POST');
     });
 
     it("should flush the log's events", () => {
@@ -88,55 +90,61 @@ describe('Logger', () => {
       Logger.start(log);
       Logger.append(eventNames[0], { test: 'event' });
       const event = log.events[eventNames[0]][0];
-      expect(event).to.deep.include({ test: 'event' });
-      Logger.end(log);
-      expect(log.events).to.deep.equal({});
-    });
-
-    it('should include ts, start_offset, event_name, impression_id, source, and source_id in every event', () => {
-      const config = {
-        eventNames: ['event1', 'event2'],
-        impressionId: 'impress_me',
-        source: 'superset',
-        sourceId: 'lolz',
-      };
-      const log = setup(config);
-
-      Logger.start(log);
-      Logger.append('event1', { key: 'value' });
-      Logger.append('event2', { foo: 'bar' });
+      expect(event).toMatchObject({ test: 'event' });
       Logger.end(log);
-
-      const args = $.ajax.getCall(0).args[0];
-      const events = JSON.parse(args.data.events);
-
-      expect(events).to.have.length(2);
-      expect(events[0]).to.deep.include({
-        key: 'value',
-        event_name: 'event1',
-        impression_id: config.impressionId,
-        source: config.source,
-        source_id: config.sourceId,
-      });
-      expect(events[1]).to.deep.include({
-        foo: 'bar',
-        event_name: 'event2',
-        impression_id: config.impressionId,
-        source: config.source,
-        source_id: config.sourceId,
-      });
-      expect(typeof events[0].ts).to.equal('number');
-      expect(typeof events[1].ts).to.equal('number');
-      expect(typeof events[0].start_offset).to.equal('number');
-      expect(typeof events[1].start_offset).to.equal('number');
+      expect(log.events).toEqual({});
     });
 
-    it('should send() a log immediately if .append() is called with sendNow=true', () => {
-      const log = setup();
-      Logger.start(log);
-      Logger.append(eventNames[0], { test: 'event' }, true);
-      expect($.ajax.calledOnce).to.equal(true);
-      Logger.end(log);
-    });
+    it(
+      'should include ts, start_offset, event_name, impression_id, source, and source_id in every event',
+      () => {
+        const config = {
+          eventNames: ['event1', 'event2'],
+          impressionId: 'impress_me',
+          source: 'superset',
+          sourceId: 'lolz',
+        };
+        const log = setup(config);
+
+        Logger.start(log);
+        Logger.append('event1', { key: 'value' });
+        Logger.append('event2', { foo: 'bar' });
+        Logger.end(log);
+
+        const args = $.ajax.getCall(0).args[0];
+        const events = JSON.parse(args.data.events);
+
+        expect(events).toHaveLength(2);
+        expect(events[0]).toMatchObject({
+          key: 'value',
+          event_name: 'event1',
+          impression_id: config.impressionId,
+          source: config.source,
+          source_id: config.sourceId,
+        });
+        expect(events[1]).toMatchObject({
+          foo: 'bar',
+          event_name: 'event2',
+          impression_id: config.impressionId,
+          source: config.source,
+          source_id: config.sourceId,
+        });
+        expect(typeof events[0].ts).toBe('number');
+        expect(typeof events[1].ts).toBe('number');
+        expect(typeof events[0].start_offset).toBe('number');
+        expect(typeof events[1].start_offset).toBe('number');
+      },
+    );
+
+    it(
+      'should send() a log immediately if .append() is called with sendNow=true',
+      () => {
+        const log = setup();
+        Logger.start(log);
+        Logger.append(eventNames[0], { test: 'event' }, true);
+        expect($.ajax.calledOnce).toBe(true);
+        Logger.end(log);
+      },
+    );
   });
 });
diff --git a/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx b/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx
index ed39d7c..6348b93 100644
--- a/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx
+++ b/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import mockMessageToasts from '../mockMessageToasts';
 import Toast from '../../../../src/messageToasts/components/Toast';
@@ -19,12 +18,12 @@ describe('ToastPresenter', () => {
 
   it('should render a div with class toast-presenter', () => {
     const wrapper = setup();
-    expect(wrapper.find('.toast-presenter')).to.have.length(1);
+    expect(wrapper.find('.toast-presenter')).toHaveLength(1);
   });
 
   it('should render a Toast for each toast object', () => {
     const wrapper = setup();
-    expect(wrapper.find(Toast)).to.have.length(props.toasts.length);
+    expect(wrapper.find(Toast)).toHaveLength(props.toasts.length);
   });
 
   it('should pass removeToast to the Toast component', () => {
@@ -35,6 +34,6 @@ describe('ToastPresenter', () => {
         .find(Toast)
         .first()
         .prop('onCloseToast'),
-    ).to.equal(removeToast);
+    ).toBe(removeToast);
   });
 });
diff --git a/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx
index 2ecf889..adb4257 100644
--- a/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx
+++ b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx
@@ -1,7 +1,6 @@
 import { Alert } from 'react-bootstrap';
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import mockMessageToasts from '../mockMessageToasts';
 import Toast from '../../../../src/messageToasts/components/Toast';
@@ -19,24 +18,24 @@ describe('Toast', () => {
 
   it('should render an Alert', () => {
     const wrapper = setup();
-    expect(wrapper.find(Alert)).to.have.length(1);
+    expect(wrapper.find(Alert)).toHaveLength(1);
   });
 
   it('should render toastText within the alert', () => {
     const wrapper = setup();
     const alert = wrapper.find(Alert).dive();
 
-    expect(alert.childAt(1).text()).to.equal(props.toast.text);
+    expect(alert.childAt(1).text()).toBe(props.toast.text);
   });
 
   it('should call onCloseToast upon alert dismissal', done => {
     const onCloseToast = id => {
-      expect(id).to.equal(props.toast.id);
+      expect(id).toBe(props.toast.id);
       done();
     };
     const wrapper = setup({ onCloseToast });
     const handleClosePress = wrapper.instance().handleClosePress;
-    expect(wrapper.find(Alert).prop('onDismiss')).to.equal(handleClosePress);
+    expect(wrapper.find(Alert).prop('onDismiss')).toBe(handleClosePress);
     handleClosePress(); // there is a timeout for onCloseToast to be called
   });
 });
diff --git a/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js b/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js
index 6471ccf..3cdb7af 100644
--- a/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js
+++ b/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js
@@ -1,11 +1,9 @@
-import { expect } from 'chai';
-
 import { ADD_TOAST, REMOVE_TOAST } from '../../../../src/messageToasts/actions';
 import messageToastsReducer from '../../../../src/messageToasts/reducers';
 
 describe('messageToasts reducer', () => {
   it('should return initial state', () => {
-    expect(messageToastsReducer(undefined, {})).to.deep.equal([]);
+    expect(messageToastsReducer(undefined, {})).toEqual([]);
   });
 
   it('should add a toast', () => {
@@ -14,15 +12,15 @@ describe('messageToasts reducer', () => {
         type: ADD_TOAST,
         payload: { text: 'test', id: 'id', type: 'test_type' },
       }),
-    ).to.deep.equal([{ text: 'test', id: 'id', type: 'test_type' }]);
+    ).toEqual([{ text: 'test', id: 'id', type: 'test_type' }]);
   });
 
-  it('should add a toast', () => {
+  it('should remove a toast', () => {
     expect(
       messageToastsReducer([{ id: 'id' }, { id: 'id2' }], {
         type: REMOVE_TOAST,
         payload: { id: 'id' },
       }),
-    ).to.deep.equal([{ id: 'id2' }]);
+    ).toEqual([{ id: 'id2' }]);
   });
 });
diff --git a/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js b/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
index edcddef..ce63297 100644
--- a/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
+++ b/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
@@ -1,5 +1,3 @@
-import { expect } from 'chai';
-
 import {
   DANGER_TOAST,
   INFO_TOAST,
@@ -11,14 +9,14 @@ import getToastsFromPyFlashMessages from '../../../../src/messageToasts/utils/ge
 describe('getToastsFromPyFlashMessages', () => {
   it('should return an info toast', () => {
     const toast = getToastsFromPyFlashMessages([['info', 'info test']])[0];
-    expect(toast).to.deep.include({ toastType: INFO_TOAST, text: 'info test' });
+    expect(toast).toMatchObject({ toastType: INFO_TOAST, text: 'info test' });
   });
 
   it('should return a success toast', () => {
     const toast = getToastsFromPyFlashMessages([
       ['success', 'success test'],
     ])[0];
-    expect(toast).to.deep.include({
+    expect(toast).toMatchObject({
       toastType: SUCCESS_TOAST,
       text: 'success test',
     });
@@ -26,7 +24,7 @@ describe('getToastsFromPyFlashMessages', () => {
 
   it('should return a danger toast', () => {
     const toast = getToastsFromPyFlashMessages([['danger', 'danger test']])[0];
-    expect(toast).to.deep.include({
+    expect(toast).toMatchObject({
       toastType: DANGER_TOAST,
       text: 'danger test',
     });
diff --git a/superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js b/superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js
index 1696dd2..0be4996 100644
--- a/superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js
+++ b/superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js
@@ -1,5 +1,3 @@
-import { it, describe, before } from 'mocha';
-import { expect } from 'chai';
 import CategoricalColorNamespace, {
   getNamespace,
   getScale,
@@ -9,65 +7,71 @@ import CategoricalColorNamespace, {
 import { registerScheme } from '../../../src/modules/ColorSchemeManager';
 
 describe('CategoricalColorNamespace', () => {
-  before(() => {
+  beforeAll(() => {
     registerScheme('testColors', ['red', 'green', 'blue']);
     registerScheme('testColors2', ['red', 'green', 'blue']);
   });
   it('The class constructor cannot be accessed directly', () => {
-    expect(CategoricalColorNamespace).to.not.be.a('Function');
+    expect(typeof CategoricalColorNamespace).not.toBe('Function');
   });
   describe('static getNamespace()', () => {
     it('returns default namespace if name is not specified', () => {
       const namespace = getNamespace();
-      expect(namespace !== undefined).to.equal(true);
-      expect(namespace.name).to.equal(DEFAULT_NAMESPACE);
+      expect(namespace !== undefined).toBe(true);
+      expect(namespace.name).toBe(DEFAULT_NAMESPACE);
     });
     it('returns namespace with specified name', () => {
       const namespace = getNamespace('myNamespace');
-      expect(namespace !== undefined).to.equal(true);
-      expect(namespace.name).to.equal('myNamespace');
+      expect(namespace !== undefined).toBe(true);
+      expect(namespace.name).toBe('myNamespace');
     });
     it('returns existing instance if the name already exists', () => {
       const ns1 = getNamespace('myNamespace');
       const ns2 = getNamespace('myNamespace');
-      expect(ns1).to.equal(ns2);
+      expect(ns1).toBe(ns2);
       const ns3 = getNamespace();
       const ns4 = getNamespace();
-      expect(ns3).to.equal(ns4);
+      expect(ns3).toBe(ns4);
     });
   });
   describe('.getScale()', () => {
     it('returns a CategoricalColorScale from given scheme name', () => {
       const namespace = getNamespace('test-get-scale1');
       const scale = namespace.getScale('testColors');
-      expect(scale).to.not.equal(undefined);
-      expect(scale.getColor('dog')).to.not.equal(undefined);
-    });
-    it('returns same scale if the scale with that name already exists in this namespace', () => {
-      const namespace = getNamespace('test-get-scale2');
-      const scale1 = namespace.getScale('testColors');
-      const scale2 = namespace.getScale('testColors2');
-      const scale3 = namespace.getScale('testColors2');
-      const scale4 = namespace.getScale('testColors');
-      expect(scale1).to.equal(scale4);
-      expect(scale2).to.equal(scale3);
+      expect(scale).toBeDefined();
+      expect(scale.getColor('dog')).toBeDefined();
     });
+    it(
+      'returns same scale if the scale with that name already exists in this namespace',
+      () => {
+        const namespace = getNamespace('test-get-scale2');
+        const scale1 = namespace.getScale('testColors');
+        const scale2 = namespace.getScale('testColors2');
+        const scale3 = namespace.getScale('testColors2');
+        const scale4 = namespace.getScale('testColors');
+        expect(scale1).toBe(scale4);
+        expect(scale2).toBe(scale3);
+      },
+    );
   });
   describe('.setColor()', () => {
-    it('overwrites color for all CategoricalColorScales in this namespace', () => {
-      const namespace = getNamespace('test-set-scale1');
-      namespace.setColor('dog', 'black');
-      const scale = namespace.getScale('testColors');
-      expect(scale.getColor('dog')).to.equal('black');
-      expect(scale.getColor('boy')).to.not.equal('black');
-    });
+    it(
+      'overwrites color for all CategoricalColorScales in this namespace',
+      () => {
+        const namespace = getNamespace('test-set-scale1');
+        namespace.setColor('dog', 'black');
+        const scale = namespace.getScale('testColors');
+        expect(scale.getColor('dog')).toBe('black');
+        expect(scale.getColor('boy')).not.toBe('black');
+      },
+    );
     it('can override forcedColors in each scale', () => {
       const namespace = getNamespace('test-set-scale2');
       namespace.setColor('dog', 'black');
       const scale = namespace.getScale('testColors');
       scale.setColor('dog', 'pink');
-      expect(scale.getColor('dog')).to.equal('black');
-      expect(scale.getColor('boy')).to.not.equal('black');
+      expect(scale.getColor('dog')).toBe('black');
+      expect(scale.getColor('boy')).not.toBe('black');
     });
     it('does not affect scales in other namespaces', () => {
       const ns1 = getNamespace('test-set-scale3.1');
@@ -75,56 +79,74 @@ describe('CategoricalColorNamespace', () => {
       const scale1 = ns1.getScale('testColors');
       const ns2 = getNamespace('test-set-scale3.2');
       const scale2 = ns2.getScale('testColors');
-      expect(scale1.getColor('dog')).to.equal('black');
-      expect(scale2.getColor('dog')).to.not.equal('black');
+      expect(scale1.getColor('dog')).toBe('black');
+      expect(scale2.getColor('dog')).not.toBe('black');
     });
     it('returns the namespace instance', () => {
       const ns1 = getNamespace('test-set-scale3.1');
       const ns2 = ns1.setColor('dog', 'black');
-      expect(ns1).to.equal(ns2);
+      expect(ns1).toBe(ns2);
     });
   });
   describe('static getScale()', () => {
-    it('getScale() returns a CategoricalColorScale with default scheme in default namespace', () => {
-      const scale = getScale();
-      expect(scale).to.not.equal(undefined);
-      const scale2 = getNamespace().getScale();
-      expect(scale).to.equal(scale2);
-    });
-    it('getScale(scheme) returns a CategoricalColorScale with specified scheme in default namespace', () => {
-      const scale = getScale('testColors');
-      expect(scale).to.not.equal(undefined);
-      const scale2 = getNamespace().getScale('testColors');
-      expect(scale).to.equal(scale2);
-    });
-    it('getScale(scheme, namespace) returns a CategoricalColorScale with specified scheme in specified namespace', () => {
-      const scale = getScale('testColors', 'test-getScale');
-      expect(scale).to.not.equal(undefined);
-      const scale2 = getNamespace('test-getScale').getScale('testColors');
-      expect(scale).to.equal(scale2);
-    });
+    it(
+      'getScale() returns a CategoricalColorScale with default scheme in default namespace',
+      () => {
+        const scale = getScale();
+        expect(scale).toBeDefined();
+        const scale2 = getNamespace().getScale();
+        expect(scale).toBe(scale2);
+      },
+    );
+    it(
+      'getScale(scheme) returns a CategoricalColorScale with specified scheme in default namespace',
+      () => {
+        const scale = getScale('testColors');
+        expect(scale).toBeDefined();
+        const scale2 = getNamespace().getScale('testColors');
+        expect(scale).toBe(scale2);
+      },
+    );
+    it(
+      'getScale(scheme, namespace) returns a CategoricalColorScale with specified scheme in specified namespace',
+      () => {
+        const scale = getScale('testColors', 'test-getScale');
+        expect(scale).toBeDefined();
+        const scale2 = getNamespace('test-getScale').getScale('testColors');
+        expect(scale).toBe(scale2);
+      },
+    );
   });
   describe('static getColor()', () => {
-    it('getColor(value) returns a color from default scheme in default namespace', () => {
-      const value = 'dog';
-      const color = getColor(value);
-      const color2 = getNamespace().getScale().getColor(value);
-      expect(color).to.equal(color2);
-    });
-    it('getColor(value, scheme) returns a color from specified scheme in default namespace', () => {
-      const value = 'dog';
-      const scheme = 'testColors';
-      const color = getColor(value, scheme);
-      const color2 = getNamespace().getScale(scheme).getColor(value);
-      expect(color).to.equal(color2);
-    });
-    it('getColor(value, scheme, namespace) returns a color from specified scheme in specified namespace', () => {
-      const value = 'dog';
-      const scheme = 'testColors';
-      const namespace = 'test-getColor';
-      const color = getColor(value, scheme, namespace);
-      const color2 = getNamespace(namespace).getScale(scheme).getColor(value);
-      expect(color).to.equal(color2);
-    });
+    it(
+      'getColor(value) returns a color from default scheme in default namespace',
+      () => {
+        const value = 'dog';
+        const color = getColor(value);
+        const color2 = getNamespace().getScale().getColor(value);
+        expect(color).toBe(color2);
+      },
+    );
+    it(
+      'getColor(value, scheme) returns a color from specified scheme in default namespace',
+      () => {
+        const value = 'dog';
+        const scheme = 'testColors';
+        const color = getColor(value, scheme);
+        const color2 = getNamespace().getScale(scheme).getColor(value);
+        expect(color).toBe(color2);
+      },
+    );
+    it(
+      'getColor(value, scheme, namespace) returns a color from specified scheme in specified namespace',
+      () => {
+        const value = 'dog';
+        const scheme = 'testColors';
+        const namespace = 'test-getColor';
+        const color = getColor(value, scheme, namespace);
+        const color2 = getNamespace(namespace).getScale(scheme).getColor(value);
+        expect(color).toBe(color2);
+      },
+    );
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js b/superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js
index fc2e2ea..5cbf6e6 100644
--- a/superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js
+++ b/superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js
@@ -1,22 +1,20 @@
-import { it, describe } from 'mocha';
-import { expect } from 'chai';
 import CategoricalColorScale from '../../../src/modules/CategoricalColorScale';
 
 describe('CategoricalColorScale', () => {
   it('exists', () => {
-    expect(CategoricalColorScale !== undefined).to.equal(true);
+    expect(CategoricalColorScale !== undefined).toBe(true);
   });
 
   describe('new CategoricalColorScale(colors, parentForcedColors)', () => {
     it('can create new scale when parentForcedColors is not given', () => {
       const scale = new CategoricalColorScale(['blue', 'red', 'green']);
-      expect(scale).to.be.instanceOf(CategoricalColorScale);
+      expect(scale).toBeInstanceOf(CategoricalColorScale);
     });
     it('can create new scale when parentForcedColors is given', () => {
       const parentForcedColors = {};
       const scale = new CategoricalColorScale(['blue', 'red', 'green'], parentForcedColors);
-      expect(scale).to.be.instanceOf(CategoricalColorScale);
-      expect(scale.parentForcedColors).to.equal(parentForcedColors);
+      expect(scale).toBeInstanceOf(CategoricalColorScale);
+      expect(scale.parentForcedColors).toBe(parentForcedColors);
     });
   });
   describe('.getColor(value)', () => {
@@ -28,8 +26,8 @@ describe('CategoricalColorScale', () => {
       scale.getColor('cow');
       const c5 = scale.getColor('horse');
 
-      expect(c1).to.equal(c3);
-      expect(c2).to.equal(c5);
+      expect(c1).toBe(c3);
+      expect(c2).toBe(c5);
     });
     it('returns different color for consecutive items', () => {
       const scale = new CategoricalColorScale(['blue', 'red', 'green']);
@@ -37,9 +35,9 @@ describe('CategoricalColorScale', () => {
       const c2 = scale.getColor('horse');
       const c3 = scale.getColor('cat');
 
-      expect(c1).to.not.equal(c2);
-      expect(c2).to.not.equal(c3);
-      expect(c3).to.not.equal(c1);
+      expect(c1).not.toBe(c2);
+      expect(c2).not.toBe(c3);
+      expect(c3).not.toBe(c1);
     });
     it('recycles colors when number of items exceed available colors', () => {
       const colorSet = {};
@@ -59,9 +57,9 @@ describe('CategoricalColorScale', () => {
           colorSet[color] = 1;
         }
       });
-      expect(Object.keys(colorSet).length).to.equal(3);
+      expect(Object.keys(colorSet)).toHaveLength(3);
       ['blue', 'red', 'green'].forEach((color) => {
-        expect(colorSet[color]).to.equal(2);
+        expect(colorSet[color]).toBe(2);
       });
     });
   });
@@ -69,28 +67,28 @@ describe('CategoricalColorScale', () => {
     it('overrides default color', () => {
       const scale = new CategoricalColorScale(['blue', 'red', 'green']);
       scale.setColor('pig', 'pink');
-      expect(scale.getColor('pig')).to.equal('pink');
+      expect(scale.getColor('pig')).toBe('pink');
     });
     it('does not override parentForcedColors', () => {
       const scale1 = new CategoricalColorScale(['blue', 'red', 'green']);
       scale1.setColor('pig', 'black');
       const scale2 = new CategoricalColorScale(['blue', 'red', 'green'], scale1.forcedColors);
       scale2.setColor('pig', 'pink');
-      expect(scale1.getColor('pig')).to.equal('black');
-      expect(scale2.getColor('pig')).to.equal('black');
+      expect(scale1.getColor('pig')).toBe('black');
+      expect(scale2.getColor('pig')).toBe('black');
     });
     it('returns the scale', () => {
       const scale = new CategoricalColorScale(['blue', 'red', 'green']);
       const output = scale.setColor('pig', 'pink');
-      expect(scale).to.equal(output);
+      expect(scale).toBe(output);
     });
   });
   describe('.toFunction()', () => {
     it('returns a function that wraps getColor', () => {
       const scale = new CategoricalColorScale(['blue', 'red', 'green']);
       const colorFn = scale.toFunction();
-      expect(scale.getColor('pig')).to.equal(colorFn('pig'));
-      expect(scale.getColor('cat')).to.equal(colorFn('cat'));
+      expect(scale.getColor('pig')).toBe(colorFn('pig'));
+      expect(scale.getColor('cat')).toBe(colorFn('cat'));
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js b/superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js
index 236b1e4..2f3fe33 100644
--- a/superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js
+++ b/superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js
@@ -1,5 +1,3 @@
-import { it, describe, beforeEach } from 'mocha';
-import { expect } from 'chai';
 import ColorSchemeManager, {
   getInstance,
   getScheme,
@@ -19,30 +17,30 @@ describe('ColorSchemeManager', () => {
     m.setDefaultSchemeName('test');
   });
   it('The class constructor cannot be accessed directly', () => {
-    expect(ColorSchemeManager).to.not.be.a('Function');
+    expect(typeof ColorSchemeManager).not.toBe('Function');
   });
   describe('static getInstance()', () => {
     it('returns a singleton instance', () => {
       const m1 = getInstance();
       const m2 = getInstance();
-      expect(m1).to.not.equal(undefined);
-      expect(m1).to.equal(m2);
+      expect(m1).toBeDefined();
+      expect(m1).toBe(m2);
     });
   });
   describe('.getScheme()', () => {
     it('.getScheme() returns default color scheme', () => {
       const scheme = getInstance().getScheme();
-      expect(scheme).to.deep.equal(['red', 'green', 'blue']);
+      expect(scheme).toEqual(['red', 'green', 'blue']);
     });
     it('.getScheme(name) returns color scheme with specified name', () => {
       const scheme = getInstance().getScheme('test2');
-      expect(scheme).to.deep.equal(['orange', 'yellow', 'pink']);
+      expect(scheme).toEqual(['orange', 'yellow', 'pink']);
     });
   });
   describe('.getAllSchemes()', () => {
     it('returns all registered schemes', () => {
       const schemes = getInstance().getAllSchemes();
-      expect(schemes).to.deep.equal({
+      expect(schemes).toEqual({
         test: ['red', 'green', 'blue'],
         test2: ['orange', 'yellow', 'pink'],
       });
@@ -51,30 +49,30 @@ describe('ColorSchemeManager', () => {
   describe('.getDefaultSchemeName()', () => {
     it('returns default scheme name', () => {
       const name = getInstance().getDefaultSchemeName();
-      expect(name).to.equal('test');
+      expect(name).toBe('test');
     });
   });
   describe('.setDefaultSchemeName()', () => {
     it('set default scheme name', () => {
       getInstance().setDefaultSchemeName('test2');
       const name = getInstance().getDefaultSchemeName();
-      expect(name).to.equal('test2');
+      expect(name).toBe('test2');
       getInstance().setDefaultSchemeName('test');
     });
     it('returns the ColorSchemeManager instance', () => {
       const instance = getInstance().setDefaultSchemeName('test');
-      expect(instance).to.equal(getInstance());
+      expect(instance).toBe(getInstance());
     });
   });
   describe('.registerScheme(name, colors)', () => {
     it('sets schemename and color', () => {
       getInstance().registerScheme('test3', ['cyan', 'magenta']);
       const scheme = getInstance().getScheme('test3');
-      expect(scheme).to.deep.equal(['cyan', 'magenta']);
+      expect(scheme).toEqual(['cyan', 'magenta']);
     });
     it('returns the ColorSchemeManager instance', () => {
       const instance = getInstance().registerScheme('test3', ['cyan', 'magenta']);
-      expect(instance).to.equal(getInstance());
+      expect(instance).toBe(getInstance());
     });
   });
   describe('.registerMultipleSchemes(object)', () => {
@@ -84,38 +82,38 @@ describe('ColorSchemeManager', () => {
         test5: ['brown', 'purple'],
       });
       const scheme1 = getInstance().getScheme('test4');
-      expect(scheme1).to.deep.equal(['cyan', 'magenta']);
+      expect(scheme1).toEqual(['cyan', 'magenta']);
       const scheme2 = getInstance().getScheme('test5');
-      expect(scheme2).to.deep.equal(['brown', 'purple']);
+      expect(scheme2).toEqual(['brown', 'purple']);
     });
     it('returns the ColorSchemeManager instance', () => {
       const instance = getInstance().registerMultipleSchemes({
         test4: ['cyan', 'magenta'],
         test5: ['brown', 'purple'],
       });
-      expect(instance).to.equal(getInstance());
+      expect(instance).toBe(getInstance());
     });
   });
   describe('static getScheme()', () => {
     it('is equivalent to getInstance().getScheme()', () => {
-      expect(getInstance().getScheme()).to.equal(getScheme());
+      expect(getInstance().getScheme()).toBe(getScheme());
     });
   });
   describe('static getAllSchemes()', () => {
     it('is equivalent to getInstance().getAllSchemes()', () => {
-      expect(getInstance().getAllSchemes()).to.equal(getAllSchemes());
+      expect(getInstance().getAllSchemes()).toBe(getAllSchemes());
     });
   });
   describe('static getDefaultSchemeName()', () => {
     it('is equivalent to getInstance().getDefaultSchemeName()', () => {
-      expect(getInstance().getDefaultSchemeName()).to.equal(getDefaultSchemeName());
+      expect(getInstance().getDefaultSchemeName()).toBe(getDefaultSchemeName());
     });
   });
   describe('static setDefaultSchemeName()', () => {
     it('is equivalent to getInstance().setDefaultSchemeName()', () => {
       setDefaultSchemeName('test2');
       const name = getInstance().getDefaultSchemeName();
-      expect(name).to.equal('test2');
+      expect(name).toBe('test2');
       setDefaultSchemeName('test');
     });
   });
@@ -123,7 +121,7 @@ describe('ColorSchemeManager', () => {
     it('is equivalent to getInstance().registerScheme()', () => {
       registerScheme('test3', ['cyan', 'magenta']);
       const scheme = getInstance().getScheme('test3');
-      expect(scheme).to.deep.equal(['cyan', 'magenta']);
+      expect(scheme).toEqual(['cyan', 'magenta']);
     });
   });
   describe('static registerMultipleSchemes()', () => {
@@ -133,9 +131,9 @@ describe('ColorSchemeManager', () => {
         test5: ['brown', 'purple'],
       });
       const scheme1 = getInstance().getScheme('test4');
-      expect(scheme1).to.deep.equal(['cyan', 'magenta']);
+      expect(scheme1).toEqual(['cyan', 'magenta']);
       const scheme2 = getInstance().getScheme('test5');
-      expect(scheme2).to.deep.equal(['brown', 'purple']);
+      expect(scheme2).toEqual(['brown', 'purple']);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/Registry_spec.js b/superset/assets/spec/javascripts/modules/Registry_spec.js
index 47cdb21..5eb5791 100644
--- a/superset/assets/spec/javascripts/modules/Registry_spec.js
+++ b/superset/assets/spec/javascripts/modules/Registry_spec.js
@@ -1,21 +1,19 @@
-import { describe, it } from 'mocha';
-import { expect } from 'chai';
 import Registry from '../../../src/modules/Registry';
 
 describe('Registry', () => {
   it('exists', () => {
-    expect(Registry !== undefined).to.equal(true);
+    expect(Registry !== undefined).toBe(true);
   });
 
   describe('new Registry(name)', () => {
     it('can create a new registry when name is not given', () => {
       const registry = new Registry();
-      expect(registry).to.be.instanceOf(Registry);
+      expect(registry).toBeInstanceOf(Registry);
     });
     it('can create a new registry when name is given', () => {
       const registry = new Registry('abc');
-      expect(registry).to.be.instanceOf(Registry);
-      expect(registry.name).to.equal('abc');
+      expect(registry).toBeInstanceOf(Registry);
+      expect(registry.name).toBe('abc');
     });
   });
 
@@ -23,13 +21,13 @@ describe('Registry', () => {
     it('returns true if an item with the given key exists', () => {
       const registry = new Registry();
       registry.registerValue('a', 'testValue');
-      expect(registry.has('a')).to.equal(true);
+      expect(registry.has('a')).toBe(true);
       registry.registerLoader('b', () => 'testValue2');
-      expect(registry.has('b')).to.equal(true);
+      expect(registry.has('b')).toBe(true);
     });
     it('returns false if an item with the given key does not exist', () => {
       const registry = new Registry();
-      expect(registry.has('a')).to.equal(false);
+      expect(registry.has('a')).toBe(false);
     });
   });
 
@@ -37,12 +35,12 @@ describe('Registry', () => {
     it('registers the given value with the given key', () => {
       const registry = new Registry();
       registry.registerValue('a', 'testValue');
-      expect(registry.has('a')).to.equal(true);
-      expect(registry.get('a')).to.equal('testValue');
+      expect(registry.has('a')).toBe(true);
+      expect(registry.get('a')).toBe('testValue');
     });
     it('returns the registry itself', () => {
       const registry = new Registry();
-      expect(registry.registerValue('a', 'testValue')).to.equal(registry);
+      expect(registry.registerValue('a', 'testValue')).toBe(registry);
     });
   });
 
@@ -50,12 +48,12 @@ describe('Registry', () => {
     it('registers the given loader with the given key', () => {
       const registry = new Registry();
       registry.registerLoader('a', () => 'testValue');
-      expect(registry.has('a')).to.equal(true);
-      expect(registry.get('a')).to.equal('testValue');
+      expect(registry.has('a')).toBe(true);
+      expect(registry.get('a')).toBe('testValue');
     });
     it('returns the registry itself', () => {
       const registry = new Registry();
-      expect(registry.registerLoader('a', () => 'testValue')).to.equal(registry);
+      expect(registry.registerLoader('a', () => 'testValue')).toBe(registry);
     });
   });
 
@@ -63,59 +61,77 @@ describe('Registry', () => {
     it('given the key, returns the value if the item is a value', () => {
       const registry = new Registry();
       registry.registerValue('a', 'testValue');
-      expect(registry.get('a')).to.equal('testValue');
-    });
-    it('given the key, returns the result of the loader function if the item is a loader', () => {
-      const registry = new Registry();
-      registry.registerLoader('b', () => 'testValue2');
-      expect(registry.get('b')).to.equal('testValue2');
-    });
+      expect(registry.get('a')).toBe('testValue');
+    });
+    it(
+      'given the key, returns the result of the loader function if the item is a loader',
+      () => {
+        const registry = new Registry();
+        registry.registerLoader('b', () => 'testValue2');
+        expect(registry.get('b')).toBe('testValue2');
+      },
+    );
     it('returns null if the item with specified key does not exist', () => {
       const registry = new Registry();
-      expect(registry.get('a')).to.equal(null);
-    });
-    it('If the key was registered multiple times, returns the most recent item.', () => {
-      const registry = new Registry();
-      registry.registerValue('a', 'testValue');
-      expect(registry.get('a')).to.equal('testValue');
-      registry.registerLoader('a', () => 'newValue');
-      expect(registry.get('a')).to.equal('newValue');
-    });
+      expect(registry.get('a')).toBeNull();
+    });
+    it(
+      'If the key was registered multiple times, returns the most recent item.',
+      () => {
+        const registry = new Registry();
+        registry.registerValue('a', 'testValue');
+        expect(registry.get('a')).toBe('testValue');
+        registry.registerLoader('a', () => 'newValue');
+        expect(registry.get('a')).toBe('newValue');
+      },
+    );
   });
 
   describe('.getAsPromise(key)', () => {
-    it('given the key, returns a promise of item value if the item is a value', () => {
-      const registry = new Registry();
-      registry.registerValue('a', 'testValue');
-      return registry.getAsPromise('a').then((value) => {
-        expect(value).to.equal('testValue');
-      });
-    });
-    it('given the key, returns a promise of result of the loader function if the item is a loader ', () => {
-      const registry = new Registry();
-      registry.registerLoader('a', () => 'testValue');
-      return registry.getAsPromise('a').then((value) => {
-        expect(value).to.equal('testValue');
-      });
-    });
-    it('returns a rejected promise if the item with specified key does not exist', () => {
-      const registry = new Registry();
-      return registry.getAsPromise('a').then(null, (err) => {
-        expect(err).to.equal('Item with key "a" is not registered.');
-      });
-    });
-    it('If the key was registered multiple times, returns a promise of the most recent item.', () => {
-      const registry = new Registry();
-      registry.registerValue('a', 'testValue');
-      const promise1 = registry.getAsPromise('a').then((value) => {
-        expect(value).to.equal('testValue');
-      });
-      registry.registerLoader('a', () => 'newValue');
-      const promise2 = registry.getAsPromise('a').then((value) => {
-        expect(value).to.equal('newValue');
-      });
-      return Promise.all([promise1, promise2]);
-    });
+    it(
+      'given the key, returns a promise of item value if the item is a value',
+      () => {
+        const registry = new Registry();
+        registry.registerValue('a', 'testValue');
+        return registry.getAsPromise('a').then((value) => {
+          expect(value).toBe('testValue');
+        });
+      },
+    );
+    it(
+      'given the key, returns a promise of result of the loader function if the item is a loader ',
+      () => {
+        const registry = new Registry();
+        registry.registerLoader('a', () => 'testValue');
+        return registry.getAsPromise('a').then((value) => {
+          expect(value).toBe('testValue');
+        });
+      },
+    );
+    it(
+      'returns a rejected promise if the item with specified key does not exist',
+      () => {
+        const registry = new Registry();
+        return registry.getAsPromise('a').then(null, (err) => {
+          expect(err).toBe('Item with key "a" is not registered.');
+        });
+      },
+    );
+    it(
+      'If the key was registered multiple times, returns a promise of the most recent item.',
+      () => {
+        const registry = new Registry();
+        registry.registerValue('a', 'testValue');
+        const promise1 = registry.getAsPromise('a').then((value) => {
+          expect(value).toBe('testValue');
+        });
+        registry.registerLoader('a', () => 'newValue');
+        const promise2 = registry.getAsPromise('a').then((value) => {
+          expect(value).toBe('newValue');
+        });
+        return Promise.all([promise1, promise2]);
+      },
+    );
   });
 
   describe('.keys()', () => {
@@ -123,7 +139,7 @@ describe('Registry', () => {
       const registry = new Registry();
       registry.registerValue('a', 'testValue');
       registry.registerLoader('b', () => 'test2');
-      expect(registry.keys()).to.deep.equal(['a', 'b']);
+      expect(registry.keys()).toEqual(['a', 'b']);
     });
   });
 
@@ -132,7 +148,7 @@ describe('Registry', () => {
       const registry = new Registry();
       registry.registerValue('a', 'test1');
       registry.registerLoader('b', () => 'test2');
-      expect(registry.entries()).to.deep.equal([
+      expect(registry.entries()).toEqual([
         { key: 'a', value: 'test1' },
         { key: 'b', value: 'test2' },
       ]);
@@ -146,7 +162,7 @@ describe('Registry', () => {
       registry.registerLoader('b', () => 'test2');
       registry.registerLoader('c', () => Promise.resolve('test3'));
       return registry.entriesAsPromise().then((entries) => {
-        expect(entries).to.deep.equal([
+        expect(entries).toEqual([
           { key: 'a', value: 'test1' },
           { key: 'b', value: 'test2' },
           { key: 'c', value: 'test3' },
@@ -160,16 +176,16 @@ describe('Registry', () => {
       const registry = new Registry();
       registry.registerValue('a', 'testValue');
       registry.remove('a');
-      expect(registry.get('a')).to.equal(null);
+      expect(registry.get('a')).toBeNull();
     });
     it('does not throw error if the key does not exist', () => {
       const registry = new Registry();
-      expect(() => registry.remove('a')).to.not.throw();
+      expect(() => registry.remove('a')).not.toThrowError();
     });
     it('returns itself', () => {
       const registry = new Registry();
       registry.registerValue('a', 'testValue');
-      expect(registry.remove('a')).to.equal(registry);
+      expect(registry.remove('a')).toBe(registry);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/colors_spec.jsx b/superset/assets/spec/javascripts/modules/colors_spec.jsx
index d1084f5..62d0e3d 100644
--- a/superset/assets/spec/javascripts/modules/colors_spec.jsx
+++ b/superset/assets/spec/javascripts/modules/colors_spec.jsx
@@ -1,13 +1,12 @@
-import { expect } from 'chai';
 import { hexToRGB } from '../../../src/modules/colors';
 
 describe('colors', () => {
   it('hexToRGB converts properly', () => {
-    expect(hexToRGB('#FFFFFF')).to.have.same.members([255, 255, 255, 255]);
-    expect(hexToRGB('#000000')).to.have.same.members([0, 0, 0, 255]);
-    expect(hexToRGB('#FF0000')).to.have.same.members([255, 0, 0, 255]);
-    expect(hexToRGB('#00FF00')).to.have.same.members([0, 255, 0, 255]);
-    expect(hexToRGB('#0000FF')).to.have.same.members([0, 0, 255, 255]);
-    expect(hexToRGB('#FF0000', 128)).to.have.same.members([255, 0, 0, 128]);
+    expect(hexToRGB('#FFFFFF')).toEqual(expect.arrayContaining([255, 255, 255, 255]));
+    expect(hexToRGB('#000000')).toEqual(expect.arrayContaining([0, 0, 0, 255]));
+    expect(hexToRGB('#FF0000')).toEqual(expect.arrayContaining([255, 0, 0, 255]));
+    expect(hexToRGB('#00FF00')).toEqual(expect.arrayContaining([0, 255, 0, 255]));
+    expect(hexToRGB('#0000FF')).toEqual(expect.arrayContaining([0, 0, 255, 255]));
+    expect(hexToRGB('#FF0000', 128)).toEqual(expect.arrayContaining([255, 0, 0, 128]));
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/dates_spec.js b/superset/assets/spec/javascripts/modules/dates_spec.js
index 957d820..3f39343 100644
--- a/superset/assets/spec/javascripts/modules/dates_spec.js
+++ b/superset/assets/spec/javascripts/modules/dates_spec.js
@@ -1,4 +1,3 @@
-import { assert, expect } from 'chai';
 import {
   tickMultiFormat,
   formatDate,
@@ -12,102 +11,102 @@ import {
 
 describe('tickMultiFormat', () => {
   it('is a function', () => {
-    assert.isFunction(tickMultiFormat);
+    expect(typeof tickMultiFormat).toBe('function');
   });
 });
 
 describe('formatDate', () => {
   it('is a function', () => {
-    assert.isFunction(formatDate);
+    expect(typeof formatDate).toBe('function');
   });
 
   it('shows only year when 1st day of the year', () => {
-    expect(formatDate(new Date('2020-01-01'))).to.equal('2020');
+    expect(formatDate(new Date('2020-01-01'))).toBe('2020');
   });
 
   it('shows only month when 1st of month', () => {
-    expect(formatDate(new Date('2020-03-01'))).to.equal('March');
+    expect(formatDate(new Date('2020-03-01'))).toBe('March');
   });
 
   it('does not show day of week when it is Sunday', () => {
-    expect(formatDate(new Date('2020-03-15'))).to.equal('Mar 15');
+    expect(formatDate(new Date('2020-03-15'))).toBe('Mar 15');
   });
 
   it('shows weekday when it is not Sunday (and no ms/sec/min/hr)', () => {
-    expect(formatDate(new Date('2020-03-03'))).to.equal('Tue 03');
+    expect(formatDate(new Date('2020-03-03'))).toBe('Tue 03');
   });
 });
 
 describe('formatDateVerbose', () => {
   it('is a function', () => {
-    assert.isFunction(formatDateVerbose);
+    expect(typeof formatDateVerbose).toBe('function');
   });
 
   it('shows only year when 1st day of the year', () => {
-    expect(formatDateVerbose(new Date('2020-01-01'))).to.equal('2020');
+    expect(formatDateVerbose(new Date('2020-01-01'))).toBe('2020');
   });
 
   it('shows month and year when 1st of month', () => {
-    expect(formatDateVerbose(new Date('2020-03-01'))).to.equal('Mar 2020');
+    expect(formatDateVerbose(new Date('2020-03-01'))).toBe('Mar 2020');
   });
 
   it('shows weekday when any day of the month', () => {
-    expect(formatDateVerbose(new Date('2020-03-03'))).to.equal('Tue Mar 3');
-    expect(formatDateVerbose(new Date('2020-03-15'))).to.equal('Sun Mar 15');
+    expect(formatDateVerbose(new Date('2020-03-03'))).toBe('Tue Mar 3');
+    expect(formatDateVerbose(new Date('2020-03-15'))).toBe('Sun Mar 15');
   });
 });
 
 describe('fDuration', () => {
   it('is a function', () => {
-    assert.isFunction(fDuration);
+    expect(typeof fDuration).toBe('function');
   });
 
   it('returns a string', () => {
-    expect(fDuration(new Date(), new Date())).to.be.a('string');
+    expect(typeof fDuration(new Date(), new Date())).toBe('string');
   });
 
   it('returns the expected output', () => {
     const output = fDuration('1496293608897', '1496293623406');
-    expect(output).to.equal('00:00:14.50');
+    expect(output).toBe('00:00:14.50');
   });
 });
 
 describe('now', () => {
   it('is a function', () => {
-    assert.isFunction(now);
+    expect(typeof now).toBe('function');
   });
 
   it('returns a number', () => {
-    expect(now()).to.be.a('number');
+    expect(typeof now()).toBe('number');
   });
 });
 
 describe('epochTimeXHoursAgo', () => {
   it('is a function', () => {
-    assert.isFunction(epochTimeXHoursAgo);
+    expect(typeof epochTimeXHoursAgo).toBe('function');
   });
 
   it('returns a number', () => {
-    expect(epochTimeXHoursAgo(1)).to.be.a('number');
+    expect(typeof epochTimeXHoursAgo(1)).toBe('number');
   });
 });
 
 describe('epochTimeXDaysAgo', () => {
   it('is a function', () => {
-    assert.isFunction(epochTimeXDaysAgo);
+    expect(typeof epochTimeXDaysAgo).toBe('function');
   });
 
   it('returns a number', () => {
-    expect(epochTimeXDaysAgo(1)).to.be.a('number');
+    expect(typeof epochTimeXDaysAgo(1)).toBe('number');
   });
 });
 
 describe('epochTimeXYearsAgo', () => {
   it('is a function', () => {
-    assert.isFunction(epochTimeXYearsAgo);
+    expect(typeof epochTimeXYearsAgo).toBe('function');
   });
 
   it('returns a number', () => {
-    expect(epochTimeXYearsAgo(1)).to.be.a('number');
+    expect(typeof epochTimeXYearsAgo(1)).toBe('number');
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/geo_spec.jsx b/superset/assets/spec/javascripts/modules/geo_spec.jsx
index 18b45db..dc82c6a 100644
--- a/superset/assets/spec/javascripts/modules/geo_spec.jsx
+++ b/superset/assets/spec/javascripts/modules/geo_spec.jsx
@@ -1,26 +1,24 @@
-import { expect } from 'chai';
-
 import { unitToRadius } from '../../../src/modules/geo';
 
 const METER_TO_MILE = 1609.34;
 
 describe('unitToRadius', () => {
   it('converts to square meters', () => {
-    expect(unitToRadius('square_m', 4 * Math.PI)).to.equal(2);
+    expect(unitToRadius('square_m', 4 * Math.PI)).toBe(2);
   });
-  it('converts to square meters', () => {
-    expect(unitToRadius('square_km', 25 * Math.PI)).to.equal(5000);
+  it('converts to square kilometers', () => {
+    expect(unitToRadius('square_km', 25 * Math.PI)).toBe(5000);
   });
   it('converts to radius meters', () => {
-    expect(unitToRadius('radius_m', 1000)).to.equal(1000);
+    expect(unitToRadius('radius_m', 1000)).toBe(1000);
   });
   it('converts to radius km', () => {
-    expect(unitToRadius('radius_km', 1)).to.equal(1000);
+    expect(unitToRadius('radius_km', 1)).toBe(1000);
   });
   it('converts to radius miles', () => {
-    expect(unitToRadius('radius_miles', 1)).to.equal(METER_TO_MILE);
+    expect(unitToRadius('radius_miles', 1)).toBe(METER_TO_MILE);
   });
   it('converts to square miles', () => {
-    expect(unitToRadius('square_miles', 25 * Math.PI)).to.equal(5000 * (METER_TO_MILE / 1000));
+    expect(unitToRadius('square_miles', 25 * Math.PI)).toBe(5000 * (METER_TO_MILE / 1000));
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/sandbox_spec.jsx b/superset/assets/spec/javascripts/modules/sandbox_spec.jsx
index f86e69c..0d894f4 100644
--- a/superset/assets/spec/javascripts/modules/sandbox_spec.jsx
+++ b/superset/assets/spec/javascripts/modules/sandbox_spec.jsx
@@ -1,16 +1,14 @@
-import { expect } from 'chai';
-
 import sandboxedEval from '../../../src/modules/sandbox';
 
 describe('sandboxedEval', () => {
   it('works like a basic eval', () => {
-    expect(sandboxedEval('100')).to.equal(100);
-    expect(sandboxedEval('v => v * 2')(5)).to.equal(10);
+    expect(sandboxedEval('100')).toBe(100);
+    expect(sandboxedEval('v => v * 2')(5)).toBe(10);
   });
   it('d3 is in context and works', () => {
-    expect(sandboxedEval("l => _.find(l, s => s === 'bar')")(['foo', 'bar'])).to.equal('bar');
+    expect(sandboxedEval("l => _.find(l, s => s === 'bar')")(['foo', 'bar'])).toBe('bar');
   });
   it('passes context as expected', () => {
-    expect(sandboxedEval('foo', { foo: 'bar' })).to.equal('bar');
+    expect(sandboxedEval('foo', { foo: 'bar' })).toBe('bar');
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/time_spec.js b/superset/assets/spec/javascripts/modules/time_spec.js
index 09a0d14..de325ec 100644
--- a/superset/assets/spec/javascripts/modules/time_spec.js
+++ b/superset/assets/spec/javascripts/modules/time_spec.js
@@ -1,5 +1,3 @@
-import { expect, assert } from 'chai';
-
 import moment from 'moment';
 import { getPlaySliderParams, truncate } from '../../../src/modules/time';
 
@@ -22,14 +20,14 @@ describe('truncate', () => {
     let result;
     isoDurations.forEach(([step, expected]) => {
       result = truncate(timestamp, step);
-      expect(result.format()).to.equal(expected.format());
+      expect(result.format()).toBe(expected.format());
     });
   });
 });
 
 describe('getPlaySliderParams', () => {
   it('is a function', () => {
-    assert.isFunction(getPlaySliderParams);
+    expect(typeof getPlaySliderParams).toBe('function');
   });
 
   it('handles durations', () => {
@@ -46,14 +44,14 @@ describe('getPlaySliderParams', () => {
       moment('2018-01-10T00:00:00'),
     ].map(d => parseInt(d.format('x'), 10));
     const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, 'P2D');
-    expect(moment(start).format()).to.equal(moment('2018-01-01T00:00:00').format());
-    expect(moment(end).format()).to.equal(moment('2018-01-11T00:00:00').format());
-    expect(getStep(start)).to.equal(2 * 24 * 60 * 60 * 1000);
-    expect(values.map(v => moment(v).format())).to.eql([
+    expect(moment(start).format()).toBe(moment('2018-01-01T00:00:00').format());
+    expect(moment(end).format()).toBe(moment('2018-01-11T00:00:00').format());
+    expect(getStep(start)).toBe(2 * 24 * 60 * 60 * 1000);
+    expect(values.map(v => moment(v).format())).toEqual([
       moment('2018-01-01T00:00:00').format(),
       moment('2018-01-03T00:00:00').format(),
     ]);
-    expect(disabled).to.equal(false);
+    expect(disabled).toBe(false);
   });
 
   it('handles intervals', () => {
@@ -71,13 +69,13 @@ describe('getPlaySliderParams', () => {
     ].map(d => parseInt(d.format('x'), 10));
     // 1970-01-03 was a Saturday
     const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, 'P1W/1970-01-03T00:00:00Z');
-    expect(moment(start).format()).to.equal(moment('2017-12-30T00:00:00Z').format());  // Saturday
-    expect(moment(end).format()).to.equal(moment('2018-01-13T00:00:00Z').format());  // Saturday
-    expect(getStep(start)).to.equal(7 * 24 * 60 * 60 * 1000);
-    expect(values.map(v => moment(v).format())).to.eql([
+    expect(moment(start).format()).toBe(moment('2017-12-30T00:00:00Z').format());  // Saturday
+    expect(moment(end).format()).toBe(moment('2018-01-13T00:00:00Z').format());  // Saturday
+    expect(getStep(start)).toBe(7 * 24 * 60 * 60 * 1000);
+    expect(values.map(v => moment(v).format())).toEqual([
       moment('2017-12-30T00:00:00Z').format(),
       moment('2018-01-06T00:00:00Z').format(),
     ]);
-    expect(disabled).to.equal(false);
+    expect(disabled).toBe(false);
   });
 });
diff --git a/superset/assets/spec/javascripts/modules/utils_spec.jsx b/superset/assets/spec/javascripts/modules/utils_spec.jsx
index 0f86435..f03e10d 100644
--- a/superset/assets/spec/javascripts/modules/utils_spec.jsx
+++ b/superset/assets/spec/javascripts/modules/utils_spec.jsx
@@ -1,4 +1,3 @@
-import { expect, assert } from 'chai';
 import {
   formatSelectOptionsForRange,
   d3format,
@@ -10,83 +9,83 @@ import {
 
 describe('utils', () => {
   it('formatSelectOptionsForRange', () => {
-    expect(formatSelectOptionsForRange(0, 4)).to.deep.equal([
+    expect(formatSelectOptionsForRange(0, 4)).toEqual([
       [0, '0'],
       [1, '1'],
       [2, '2'],
       [3, '3'],
       [4, '4'],
     ]);
-    expect(formatSelectOptionsForRange(1, 2)).to.deep.equal([
+    expect(formatSelectOptionsForRange(1, 2)).toEqual([
       [1, '1'],
       [2, '2'],
     ]);
   });
   it('d3format', () => {
-    expect(d3format('.3s', 1234)).to.equal('1.23k');
-    expect(d3format('.3s', 1237)).to.equal('1.24k');
-    expect(d3format('', 1237)).to.equal('1.24k');
+    expect(d3format('.3s', 1234)).toBe('1.23k');
+    expect(d3format('.3s', 1237)).toBe('1.24k');
+    expect(d3format('', 1237)).toBe('1.24k');
   });
   describe('d3FormatPreset', () => {
     it('is a function', () => {
-      assert.isFunction(d3FormatPreset);
+      expect(typeof d3FormatPreset).toBe('function');
     });
     it('returns a working formatter', () => {
-      expect(d3FormatPreset('.3s')(3000000)).to.equal('3.00M');
+      expect(d3FormatPreset('.3s')(3000000)).toBe('3.00M');
     });
   });
   describe('d3TimeFormatPreset', () => {
     it('is a function', () => {
-      assert.isFunction(d3TimeFormatPreset);
+      expect(typeof d3TimeFormatPreset).toBe('function');
     });
     it('returns a working time formatter', () => {
-      expect(d3FormatPreset('smart_date')(0)).to.equal('1970');
+      expect(d3FormatPreset('smart_date')(0)).toBe('1970');
     });
   });
   describe('defaultNumberFormatter', () => {
-    expect(defaultNumberFormatter(10)).to.equal('10');
-    expect(defaultNumberFormatter(1)).to.equal('1');
-    expect(defaultNumberFormatter(1.0)).to.equal('1');
-    expect(defaultNumberFormatter(10.0)).to.equal('10');
-    expect(defaultNumberFormatter(10001)).to.equal('10.0k');
-    expect(defaultNumberFormatter(10100)).to.equal('10.1k');
-    expect(defaultNumberFormatter(111000000)).to.equal('111M');
-    expect(defaultNumberFormatter(0.23)).to.equal('230m');
+    expect(defaultNumberFormatter(10)).toBe('10');
+    expect(defaultNumberFormatter(1)).toBe('1');
+    expect(defaultNumberFormatter(1.0)).toBe('1');
+    expect(defaultNumberFormatter(10.0)).toBe('10');
+    expect(defaultNumberFormatter(10001)).toBe('10.0k');
+    expect(defaultNumberFormatter(10100)).toBe('10.1k');
+    expect(defaultNumberFormatter(111000000)).toBe('111M');
+    expect(defaultNumberFormatter(0.23)).toBe('230m');
 
-    expect(defaultNumberFormatter(-10)).to.equal('-10');
-    expect(defaultNumberFormatter(-1)).to.equal('-1');
-    expect(defaultNumberFormatter(-1.0)).to.equal('-1');
-    expect(defaultNumberFormatter(-10.0)).to.equal('-10');
-    expect(defaultNumberFormatter(-10001)).to.equal('-10.0k');
-    expect(defaultNumberFormatter(-10101)).to.equal('-10.1k');
-    expect(defaultNumberFormatter(-111000000)).to.equal('-111M');
-    expect(defaultNumberFormatter(-0.23)).to.equal('-230m');
+    expect(defaultNumberFormatter(-10)).toBe('-10');
+    expect(defaultNumberFormatter(-1)).toBe('-1');
+    expect(defaultNumberFormatter(-1.0)).toBe('-1');
+    expect(defaultNumberFormatter(-10.0)).toBe('-10');
+    expect(defaultNumberFormatter(-10001)).toBe('-10.0k');
+    expect(defaultNumberFormatter(-10101)).toBe('-10.1k');
+    expect(defaultNumberFormatter(-111000000)).toBe('-111M');
+    expect(defaultNumberFormatter(-0.23)).toBe('-230m');
   });
   describe('mainMetric', () => {
     it('is null when no options', () => {
-      expect(mainMetric([])).to.equal(undefined);
-      expect(mainMetric(null)).to.equal(undefined);
+      expect(mainMetric([])).toBeUndefined();
+      expect(mainMetric(null)).toBeUndefined();
     });
     it('prefers the "count" metric when first', () => {
       const metrics = [
         { metric_name: 'count' },
         { metric_name: 'foo' },
       ];
-      expect(mainMetric(metrics)).to.equal('count');
+      expect(mainMetric(metrics)).toBe('count');
     });
     it('prefers the "count" metric when not first', () => {
       const metrics = [
         { metric_name: 'foo' },
         { metric_name: 'count' },
       ];
-      expect(mainMetric(metrics)).to.equal('count');
+      expect(mainMetric(metrics)).toBe('count');
     });
     it('selects the first metric when "count" is not an option', () => {
       const metrics = [
         { metric_name: 'foo' },
         { metric_name: 'not_count' },
       ];
-      expect(mainMetric(metrics)).to.equal('foo');
+      expect(mainMetric(metrics)).toBe('foo');
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/profile/App_spec.jsx b/superset/assets/spec/javascripts/profile/App_spec.jsx
index 80e5789..0794512 100644
--- a/superset/assets/spec/javascripts/profile/App_spec.jsx
+++ b/superset/assets/spec/javascripts/profile/App_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { Col, Row, Tab } from 'react-bootstrap';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import { user } from './fixtures';
 import App from '../../../src/profile/components/App';
@@ -13,15 +12,15 @@ describe('App', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<App {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders 2 Col', () => {
     const wrapper = mount(<App {...mockedProps} />);
-    expect(wrapper.find(Row)).to.have.length(1);
-    expect(wrapper.find(Col)).to.have.length(2);
+    expect(wrapper.find(Row)).toHaveLength(1);
+    expect(wrapper.find(Col)).toHaveLength(2);
   });
   it('renders 4 Tabs', () => {
     const wrapper = mount(<App {...mockedProps} />);
-    expect(wrapper.find(Tab)).to.have.length(4);
+    expect(wrapper.find(Tab)).toHaveLength(4);
   });
 });
diff --git a/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx b/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx
index 04b0079..a341c3e 100644
--- a/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx
+++ b/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import { user } from './fixtures';
 import CreatedContent from '../../../src/profile/components/CreatedContent';
 import TableLoader from '../../../src/components/TableLoader';
@@ -13,14 +12,14 @@ describe('CreatedContent', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<CreatedContent {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders 2 TableLoader', () => {
     const wrapper = mount(<CreatedContent {...mockedProps} />);
-    expect(wrapper.find(TableLoader)).to.have.length(2);
+    expect(wrapper.find(TableLoader)).toHaveLength(2);
   });
   it('renders 2 titles', () => {
     const wrapper = mount(<CreatedContent {...mockedProps} />);
-    expect(wrapper.find('h3')).to.have.length(2);
+    expect(wrapper.find('h3')).toHaveLength(2);
   });
 });
diff --git a/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx b/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx
index 5387322..1526b06 100644
--- a/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx
+++ b/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import sinon from 'sinon';
-import { expect } from 'chai';
 
 import EditableTable from '../../../src/components/EditableTitle';
 
@@ -22,22 +21,22 @@ describe('EditableTitle', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<EditableTable {...mockProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('should render title', () => {
     const titleElement = editableWrapper.find('input');
-    expect(titleElement.props().value).to.equal('my title');
-    expect(titleElement.props().type).to.equal('button');
+    expect(titleElement.props().value).toBe('my title');
+    expect(titleElement.props().type).toBe('button');
   });
 
   describe('should handle click', () => {
     it('should change title', () => {
       editableWrapper.find('input').simulate('click');
-      expect(editableWrapper.find('input').props().type).to.equal('text');
+      expect(editableWrapper.find('input').props().type).toBe('text');
     });
     it('should not change title', () => {
       notEditableWrapper.find('input').simulate('click');
-      expect(notEditableWrapper.find('input').props().type).to.equal('button');
+      expect(notEditableWrapper.find('input').props().type).toBe('button');
     });
   });
 
@@ -48,18 +47,18 @@ describe('EditableTitle', () => {
     });
     it('should change title', () => {
       editableWrapper.find('input').simulate('change', mockEvent);
-      expect(editableWrapper.find('input').props().value).to.equal('new title');
+      expect(editableWrapper.find('input').props().value).toBe('new title');
     });
     it('should not change title', () => {
       notEditableWrapper.find('input').simulate('change', mockEvent);
-      expect(editableWrapper.find('input').props().value).to.equal('my title');
+      expect(editableWrapper.find('input').props().value).toBe('my title');
     });
   });
 
   describe('should handle blur', () => {
     beforeEach(() => {
       editableWrapper.find('input').simulate('click');
-      expect(editableWrapper.find('input').props().type).to.equal('text');
+      expect(editableWrapper.find('input').props().type).toBe('text');
     });
     afterEach(() => {
       callback.reset();
@@ -70,22 +69,22 @@ describe('EditableTitle', () => {
     it('should trigger callback', () => {
       editableWrapper.setState({ title: 'new title' });
       editableWrapper.find('input').simulate('blur');
-      expect(editableWrapper.find('input').props().type).to.equal('button');
-      expect(callback.callCount).to.equal(1);
-      expect(callback.getCall(0).args[0]).to.equal('new title');
+      expect(editableWrapper.find('input').props().type).toBe('button');
+      expect(callback.callCount).toBe(1);
+      expect(callback.getCall(0).args[0]).toBe('new title');
     });
     it('should not trigger callback', () => {
       editableWrapper.find('input').simulate('blur');
-      expect(editableWrapper.find('input').props().type).to.equal('button');
+      expect(editableWrapper.find('input').props().type).toBe('button');
       // no change
-      expect(callback.callCount).to.equal(0);
+      expect(callback.callCount).toBe(0);
     });
     it('should not save empty title', () => {
       editableWrapper.setState({ title: '' });
       editableWrapper.find('input').simulate('blur');
-      expect(editableWrapper.find('input').props().type).to.equal('button');
-      expect(editableWrapper.find('input').props().value).to.equal('my title');
-      expect(callback.callCount).to.equal(0);
+      expect(editableWrapper.find('input').props().type).toBe('button');
+      expect(editableWrapper.find('input').props().value).toBe('my title');
+      expect(callback.callCount).toBe(0);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/profile/Favorites_spec.jsx b/superset/assets/spec/javascripts/profile/Favorites_spec.jsx
index b35817c..16efa90 100644
--- a/superset/assets/spec/javascripts/profile/Favorites_spec.jsx
+++ b/superset/assets/spec/javascripts/profile/Favorites_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import { user } from './fixtures';
 import Favorites from '../../../src/profile/components/Favorites';
@@ -13,14 +12,14 @@ describe('Favorites', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<Favorites {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders 2 TableLoader', () => {
     const wrapper = mount(<Favorites {...mockedProps} />);
-    expect(wrapper.find(TableLoader)).to.have.length(2);
+    expect(wrapper.find(TableLoader)).toHaveLength(2);
   });
   it('renders 2 titles', () => {
     const wrapper = mount(<Favorites {...mockedProps} />);
-    expect(wrapper.find('h3')).to.have.length(2);
+    expect(wrapper.find('h3')).toHaveLength(2);
   });
 });
diff --git a/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx b/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx
index 0122e67..b6deeeb 100644
--- a/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx
+++ b/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import { user } from './fixtures';
 import RecentActivity from '../../../src/profile/components/RecentActivity';
@@ -14,10 +13,10 @@ describe('RecentActivity', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<RecentActivity {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders a TableLoader', () => {
     const wrapper = mount(<RecentActivity {...mockedProps} />);
-    expect(wrapper.find(TableLoader)).to.have.length(1);
+    expect(wrapper.find(TableLoader)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/profile/Security_spec.jsx b/superset/assets/spec/javascripts/profile/Security_spec.jsx
index e544c45..463978f 100644
--- a/superset/assets/spec/javascripts/profile/Security_spec.jsx
+++ b/superset/assets/spec/javascripts/profile/Security_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import { user, userNoPerms } from './fixtures';
 import Security from '../../../src/profile/components/Security';
@@ -13,23 +12,23 @@ describe('Security', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<Security {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders 2 role labels', () => {
     const wrapper = mount(<Security {...mockedProps} />);
-    expect(wrapper.find('.roles').find('.label')).to.have.length(2);
+    expect(wrapper.find('.roles').find('.label')).toHaveLength(2);
   });
   it('renders 2 datasource labels', () => {
     const wrapper = mount(<Security {...mockedProps} />);
-    expect(wrapper.find('.datasources').find('.label')).to.have.length(2);
+    expect(wrapper.find('.datasources').find('.label')).toHaveLength(2);
   });
   it('renders 3 database labels', () => {
     const wrapper = mount(<Security {...mockedProps} />);
-    expect(wrapper.find('.databases').find('.label')).to.have.length(3);
+    expect(wrapper.find('.databases').find('.label')).toHaveLength(3);
   });
   it('renders no permission label when empty', () => {
     const wrapper = mount(<Security user={userNoPerms} />);
-    expect(wrapper.find('.datasources').find('.label')).to.have.length(0);
-    expect(wrapper.find('.databases').find('.label')).to.have.length(0);
+    expect(wrapper.find('.datasources').find('.label')).toHaveLength(0);
+    expect(wrapper.find('.databases').find('.label')).toHaveLength(0);
   });
 });
diff --git a/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx b/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx
index 2c5b3e5..e70bb00 100644
--- a/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx
+++ b/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx
@@ -2,7 +2,6 @@ import React from 'react';
 import Gravatar from 'react-gravatar';
 import { Panel } from 'react-bootstrap';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import { user } from './fixtures';
 import UserInfo from '../../../src/profile/components/UserInfo';
@@ -15,26 +14,26 @@ describe('UserInfo', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<UserInfo {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders a Gravatar', () => {
     const wrapper = mount(<UserInfo {...mockedProps} />);
-    expect(wrapper.find(Gravatar)).to.have.length(1);
+    expect(wrapper.find(Gravatar)).toHaveLength(1);
   });
   it('renders a Panel', () => {
     const wrapper = mount(<UserInfo {...mockedProps} />);
-    expect(wrapper.find(Panel)).to.have.length(1);
+    expect(wrapper.find(Panel)).toHaveLength(1);
   });
   it('renders 5 icons', () => {
     const wrapper = mount(<UserInfo {...mockedProps} />);
-    expect(wrapper.find('i')).to.have.length(5);
+    expect(wrapper.find('i')).toHaveLength(5);
   });
   it('renders roles information', () => {
     const wrapper = mount(<UserInfo {...mockedProps} />);
-    expect(wrapper.find('.roles').text()).to.equal(' Alpha, sql_lab');
+    expect(wrapper.find('.roles').text()).toBe(' Alpha, sql_lab');
   });
   it('shows the right user-id', () => {
     const wrapper = mount(<UserInfo {...mockedProps} />);
-    expect(wrapper.find('.user-id').text()).to.equal('5');
+    expect(wrapper.find('.user-id').text()).toBe('5');
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/App_spec.jsx b/superset/assets/spec/javascripts/sqllab/App_spec.jsx
index bee2569..a22645b 100644
--- a/superset/assets/spec/javascripts/sqllab/App_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/App_spec.jsx
@@ -3,7 +3,6 @@ import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
 
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import App from '../../../src/SqlLab/components/App';
@@ -16,7 +15,7 @@ describe('SqlLab App', () => {
   let store;
   let wrapper;
 
-  before(() => {
+  beforeAll(() => {
     const bootstrapData = {
       common: {
         feature_flags: {
@@ -32,24 +31,24 @@ describe('SqlLab App', () => {
   });
 
   it('should set feature flags', () => {
-    expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).to.equal(true);
+    expect(wrapper.prop('isFeatureEnabled')('FOO_BAR')).toBe(true);
   });
 
   it('is valid', () => {
-    expect(React.isValidElement(<App />)).to.equal(true);
+    expect(React.isValidElement(<App />)).toBe(true);
   });
 
   it('should handler resize', () => {
     const inner = wrapper.dive();
     sinon.spy(inner.instance(), 'getHeight');
     inner.instance().handleResize();
-    expect(inner.instance().getHeight.callCount).to.equal(1);
+    expect(inner.instance().getHeight.callCount).toBe(1);
     inner.instance().getHeight.restore();
   });
 
   it('should render', () => {
     const inner = wrapper.dive();
-    expect(inner.find('.SqlLab')).to.have.length(1);
-    expect(inner.find(TabbedSqlEditors)).to.have.length(1);
+    expect(inner.find('.SqlLab')).toHaveLength(1);
+    expect(inner.find(TabbedSqlEditors)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx b/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx
index 7ea6862..f86dc18 100644
--- a/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 
 import { mockedActions, table } from './fixtures';
 import ColumnElement from '../../../src/SqlLab/components/ColumnElement';
@@ -14,22 +13,22 @@ describe('ColumnElement', () => {
   it('is valid with props', () => {
     expect(
       React.isValidElement(<ColumnElement {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders a proper primary key', () => {
     const wrapper = mount(<ColumnElement column={table.columns[0]} />);
-    expect(wrapper.find('i.fa-key')).to.have.length(1);
-    expect(wrapper.find('.col-name').first().text()).to.equal('id');
+    expect(wrapper.find('i.fa-key')).toHaveLength(1);
+    expect(wrapper.find('.col-name').first().text()).toBe('id');
   });
   it('renders a multi-key column', () => {
     const wrapper = mount(<ColumnElement column={table.columns[1]} />);
-    expect(wrapper.find('i.fa-link')).to.have.length(1);
-    expect(wrapper.find('i.fa-bookmark')).to.have.length(1);
-    expect(wrapper.find('.col-name').first().text()).to.equal('first_name');
+    expect(wrapper.find('i.fa-link')).toHaveLength(1);
+    expect(wrapper.find('i.fa-bookmark')).toHaveLength(1);
+    expect(wrapper.find('.col-name').first().text()).toBe('first_name');
   });
   it('renders a column with no keys', () => {
     const wrapper = mount(<ColumnElement column={table.columns[2]} />);
-    expect(wrapper.find('i')).to.have.length(0);
-    expect(wrapper.find('.col-name').first().text()).to.equal('last_name');
+    expect(wrapper.find('i')).toHaveLength(0);
+    expect(wrapper.find('.col-name').first().text()).toBe('last_name');
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx b/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx
index e8f8438..dd23016 100644
--- a/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx
@@ -1,5 +1,4 @@
 import React from 'react';
-import { expect } from 'chai';
 import { initialState } from './fixtures';
 
 import CopyQueryTabUrl from '../../../src/SqlLab/components/CopyQueryTabUrl';
@@ -11,6 +10,6 @@ describe('CopyQueryTabUrl', () => {
   it('is valid with props', () => {
     expect(
       React.isValidElement(<CopyQueryTabUrl {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx b/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
index 6d78929..06b18ae 100644
--- a/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
@@ -3,7 +3,6 @@ import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
 
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import $ from 'jquery';
@@ -65,13 +64,13 @@ describe('ExploreResultsButton', () => {
     }).dive());
 
   it('renders', () => {
-    expect(React.isValidElement(<ExploreResultsButton />)).to.equal(true);
+    expect(React.isValidElement(<ExploreResultsButton />)).toBe(true);
   });
 
   it('renders with props', () => {
     expect(
       React.isValidElement(<ExploreResultsButton {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
 
   it('detects bad columns', () => {
@@ -82,15 +81,15 @@ describe('ExploreResultsButton', () => {
     });
 
     const badCols = wrapper.instance().getInvalidColumns();
-    expect(badCols).to.deep.equal(['COUNT(*)', '1', '123', 'CASE WHEN 1=1 THEN 1 ELSE 0 END']);
+    expect(badCols).toEqual(['COUNT(*)', '1', '123', 'CASE WHEN 1=1 THEN 1 ELSE 0 END']);
 
     const msgWrapper = shallow(wrapper.instance().renderInvalidColumnMessage());
-    expect(msgWrapper.find('div')).to.have.length(1);
+    expect(msgWrapper.find('div')).toHaveLength(1);
   });
 
   it('renders a Button', () => {
     const wrapper = getExploreResultsButtonWrapper();
-    expect(wrapper.find(Button)).to.have.length(1);
+    expect(wrapper.find(Button)).toHaveLength(1);
   });
 
   describe('datasourceName', () => {
@@ -107,19 +106,19 @@ describe('ExploreResultsButton', () => {
     it('should generate data source name from query', () => {
       const sampleQuery = queries[0];
       const name = wrapper.instance().datasourceName();
-      expect(name).to.equal(`${sampleQuery.user}-${sampleQuery.tab}-abcd`);
+      expect(name).toBe(`${sampleQuery.user}-${sampleQuery.tab}-abcd`);
     });
     it('should generate data source name with empty query', () => {
       wrapper.setProps({ query: {} });
       const name = wrapper.instance().datasourceName();
-      expect(name).to.equal('undefined-abcd');
+      expect(name).toBe('undefined-abcd');
     });
 
     it('should build viz options', () => {
       wrapper.setState({ chartType: mockChartTypeTB });
       const spy = sinon.spy(wrapper.instance(), 'buildVizOptions');
       wrapper.instance().buildVizOptions();
-      expect(spy.returnValues[0]).to.deep.equal({
+      expect(spy.returnValues[0]).toEqual({
         schema: 'test_schema',
         sql: wrapper.instance().props.query.sql,
         dbId: wrapper.instance().props.query.dbId,
@@ -141,7 +140,7 @@ describe('ExploreResultsButton', () => {
       context: { store },
     }).dive();
     const inst = longQueryWrapper.instance();
-    expect(inst.getQueryDuration()).to.equal(100.7050400390625);
+    expect(inst.getQueryDuration()).toBe(100.7050400390625);
   });
 
   describe('visualize', () => {
@@ -173,12 +172,12 @@ describe('ExploreResultsButton', () => {
 
     it('should build request', () => {
       wrapper.instance().visualize();
-      expect(ajaxSpy.callCount).to.equal(1);
+      expect(ajaxSpy.callCount).toBe(1);
 
       const spyCall = ajaxSpy.getCall(0);
-      expect(spyCall.args[0].type).to.equal('POST');
-      expect(spyCall.args[0].url).to.equal('/superset/sqllab_viz/');
-      expect(spyCall.args[0].data.data).to.equal(JSON.stringify(mockOptions));
+      expect(spyCall.args[0].type).toBe('POST');
+      expect(spyCall.args[0].url).toBe('/superset/sqllab_viz/');
+      expect(spyCall.args[0].data.data).toBe(JSON.stringify(mockOptions));
     });
     it('should open new window', () => {
       const infoToastSpy = sinon.spy();
@@ -197,9 +196,9 @@ describe('ExploreResultsButton', () => {
       });
 
       wrapper.instance().visualize();
-      expect(exploreUtils.exportChart.callCount).to.equal(1);
-      expect(exploreUtils.exportChart.getCall(0).args[0].datasource).to.equal('107__table');
-      expect(infoToastSpy.callCount).to.equal(1);
+      expect(exploreUtils.exportChart.callCount).toBe(1);
+      expect(exploreUtils.exportChart.getCall(0).args[0].datasource).toBe('107__table');
+      expect(infoToastSpy.callCount).toBe(1);
     });
     it('should add error toast', () => {
       const dangerToastSpy = sinon.spy();
@@ -219,8 +218,8 @@ describe('ExploreResultsButton', () => {
       });
 
       wrapper.instance().visualize();
-      expect(exploreUtils.exportChart.callCount).to.equal(0);
-      expect(dangerToastSpy.callCount).to.equal(1);
+      expect(exploreUtils.exportChart.callCount).toBe(0);
+      expect(dangerToastSpy.callCount).toBe(1);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx b/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx
index b8bc395..699a091 100644
--- a/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import SyntaxHighlighter from 'react-syntax-highlighter';
 import { mount, shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import HighlightedSql from '../../../src/SqlLab/components/HighlightedSql';
 import ModalTrigger from '../../../src/components/ModalTrigger';
@@ -10,26 +9,25 @@ import ModalTrigger from '../../../src/components/ModalTrigger';
 describe('HighlightedSql', () => {
   const sql = "SELECT * FROM test WHERE something='fkldasjfklajdslfkjadlskfjkldasjfkladsjfkdjsa'";
   it('renders with props', () => {
-    expect(React.isValidElement(<HighlightedSql sql={sql} />))
-    .to.equal(true);
+    expect(React.isValidElement(<HighlightedSql sql={sql} />)).toBe(true);
   });
   it('renders a ModalTrigger', () => {
     const wrapper = shallow(<HighlightedSql sql={sql} />);
-    expect(wrapper.find(ModalTrigger)).to.have.length(1);
+    expect(wrapper.find(ModalTrigger)).toHaveLength(1);
   });
   it('renders a ModalTrigger while using shrink', () => {
     const wrapper = shallow(<HighlightedSql sql={sql} shrink maxWidth={20} />);
-    expect(wrapper.find(ModalTrigger)).to.have.length(1);
+    expect(wrapper.find(ModalTrigger)).toHaveLength(1);
   });
   it('renders two SyntaxHighlighter in modal', () => {
     const wrapper = mount(
       <HighlightedSql sql={sql} rawSql="SELECT * FORM foo" shrink maxWidth={5} />);
     const pre = wrapper.find('pre');
-    expect(pre).to.have.length(1);
+    expect(pre).toHaveLength(1);
     pre.simulate('click');
     setTimeout(() => {
       const modalBody = mount(wrapper.state().modalBody);
-      expect(modalBody.find(SyntaxHighlighter)).to.have.length(2);
+      expect(modalBody.find(SyntaxHighlighter)).toHaveLength(2);
     }, 10);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/Link_spec.jsx b/superset/assets/spec/javascripts/sqllab/Link_spec.jsx
index f37dd6f..5be2b77 100644
--- a/superset/assets/spec/javascripts/sqllab/Link_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/Link_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import Link from '../../../src/SqlLab/components/Link';
 
@@ -10,15 +9,15 @@ describe('Link', () => {
     href: 'http://www.airbnb.com',
   };
   it('renders', () => {
-    expect(React.isValidElement(<Link>TEST</Link>)).to.equal(true);
+    expect(React.isValidElement(<Link>TEST</Link>)).toBe(true);
   });
   it('renders with props', () => {
     expect(
       React.isValidElement(<Link {...mockedProps} >TEST</Link>),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders an anchor tag', () => {
     const wrapper = shallow(<Link {...mockedProps} >TEST</Link>);
-    expect(wrapper.find('a')).to.have.length(1);
+    expect(wrapper.find('a')).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx b/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx
index 66fbf02..10d7ac9 100644
--- a/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx
@@ -2,7 +2,6 @@ import React from 'react';
 import Select from 'react-select';
 import { Button } from 'react-bootstrap';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import QuerySearch from '../../../src/SqlLab/components/QuerySearch';
@@ -16,7 +15,7 @@ describe('QuerySearch', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<QuerySearch {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   let wrapper;
   beforeEach(() => {
@@ -24,51 +23,51 @@ describe('QuerySearch', () => {
   });
 
   it('should have three Select', () => {
-    expect(wrapper.find(Select)).to.have.length(3);
+    expect(wrapper.find(Select)).toHaveLength(3);
   });
 
   it('updates fromTime on user selects from time', () => {
     wrapper.find('[name="select-from"]')
       .simulate('change', { value: 0 });
-    expect(wrapper.state().from).to.equal(0);
+    expect(wrapper.state().from).toBe(0);
   });
 
   it('updates toTime on user selects to time', () => {
     wrapper.find('[name="select-to"]')
       .simulate('change', { value: 0 });
-    expect(wrapper.state().to).to.equal(0);
+    expect(wrapper.state().to).toBe(0);
   });
 
   it('updates status on user selects status', () => {
     wrapper.find('[name="select-status"]')
       .simulate('change', { value: 'success' });
-    expect(wrapper.state().status).to.equal('success');
+    expect(wrapper.state().status).toBe('success');
   });
 
   it('should have one input for searchText', () => {
-    expect(wrapper.find('input')).to.have.length(1);
+    expect(wrapper.find('input')).toHaveLength(1);
   });
 
   it('updates search text on user inputs search text', () => {
     wrapper.find('input').simulate('change', { target: { value: 'text' } });
-    expect(wrapper.state().searchText).to.equal('text');
+    expect(wrapper.state().searchText).toBe('text');
   });
 
   it('refreshes queries when enter (only) is pressed on the input', () => {
     const callCount = search.callCount;
     wrapper.find('input').simulate('keyDown', { keyCode: 'a'.charCodeAt(0) });
-    expect(search.callCount).to.equal(callCount);
+    expect(search.callCount).toBe(callCount);
     wrapper.find('input').simulate('keyDown', { keyCode: '\r'.charCodeAt(0) });
-    expect(search.callCount).to.equal(callCount + 1);
+    expect(search.callCount).toBe(callCount + 1);
   });
 
   it('should have one Button', () => {
-    expect(wrapper.find(Button)).to.have.length(1);
+    expect(wrapper.find(Button)).toHaveLength(1);
   });
 
   it('refreshes queries when clicked', () => {
     const callCount = search.callCount;
     wrapper.find(Button).simulate('click');
-    expect(search.callCount).to.equal(callCount + 1);
+    expect(search.callCount).toBe(callCount + 1);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx b/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx
index bca5c19..628e35a 100644
--- a/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { Label } from 'react-bootstrap';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import QueryStateLabel from '../../../src/SqlLab/components/QueryStateLabel';
 
@@ -14,10 +13,10 @@ describe('SavedQuery', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<QueryStateLabel {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('has an Overlay and a Popover', () => {
     const wrapper = shallow(<QueryStateLabel {...mockedProps} />);
-    expect(wrapper.find(Label)).to.have.length(1);
+    expect(wrapper.find(Label)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx b/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx
index c985750..26e3162 100644
--- a/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import { Table } from 'reactable';
 
 import { queries } from './fixtures';
@@ -11,17 +10,17 @@ describe('QueryTable', () => {
     queries,
   };
   it('is valid', () => {
-    expect(React.isValidElement(<QueryTable />)).to.equal(true);
+    expect(React.isValidElement(<QueryTable />)).toBe(true);
   });
   it('is valid with props', () => {
     expect(
       React.isValidElement(<QueryTable {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders a proper table', () => {
     const wrapper = shallow(<QueryTable {...mockedProps} />);
-    expect(wrapper.find(Table)).to.have.length(1);
-    expect(wrapper.find(Table).shallow().find('table')).to.have.length(1);
-    expect(wrapper.find(Table).shallow().find('table').find('Tr')).to.have.length(2);
+    expect(wrapper.find(Table)).toHaveLength(1);
+    expect(wrapper.find(Table).shallow().find('table')).toHaveLength(1);
+    expect(wrapper.find(Table).shallow().find('table').find('Tr')).toHaveLength(2);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx b/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx
index d3f3690..8f30623 100644
--- a/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import { Alert, ProgressBar } from 'react-bootstrap';
@@ -41,11 +40,11 @@ describe('ResultSet', () => {
   };
 
   it('is valid', () => {
-    expect(React.isValidElement(<ResultSet {...mockedProps} />)).to.equal(true);
+    expect(React.isValidElement(<ResultSet {...mockedProps} />)).toBe(true);
   });
   it('renders a Table', () => {
     const wrapper = shallow(<ResultSet {...mockedProps} />);
-    expect(wrapper.find(FilterableTable)).to.have.length(1);
+    expect(wrapper.find(FilterableTable)).toHaveLength(1);
   });
   describe('componentWillReceiveProps', () => {
     const wrapper = shallow(<ResultSet {...mockedProps} />);
@@ -61,19 +60,19 @@ describe('ResultSet', () => {
     it('should update cached data', () => {
       wrapper.setProps(newProps);
 
-      expect(wrapper.state().data).to.deep.equal(newProps.query.results.data);
-      expect(clearQuerySpy.callCount).to.equal(1);
-      expect(clearQuerySpy.getCall(0).args[0]).to.deep.equal(newProps.query);
-      expect(fetchQuerySpy.callCount).to.equal(1);
-      expect(fetchQuerySpy.getCall(0).args[0]).to.deep.equal(newProps.query);
+      expect(wrapper.state().data).toEqual(newProps.query.results.data);
+      expect(clearQuerySpy.callCount).toBe(1);
+      expect(clearQuerySpy.getCall(0).args[0]).toEqual(newProps.query);
+      expect(fetchQuerySpy.callCount).toBe(1);
+      expect(fetchQuerySpy.getCall(0).args[0]).toEqual(newProps.query);
     });
   });
   describe('render', () => {
     it('should render success query', () => {
       const wrapper = shallow(<ResultSet {...mockedProps} />);
       const filterableTable = wrapper.find(FilterableTable);
-      expect(filterableTable.props().data).to.equal(mockedProps.query.results.data);
-      expect(wrapper.find(ExploreResultsButton)).to.have.length(1);
+      expect(filterableTable.props().data).toBe(mockedProps.query.results.data);
+      expect(wrapper.find(ExploreResultsButton)).toHaveLength(1);
     });
     it('should render empty results', () => {
       const wrapper = shallow(<ResultSet {...mockedProps} />);
@@ -83,9 +82,9 @@ describe('ResultSet', () => {
         },
       });
       wrapper.setProps({ query: emptyResults });
-      expect(wrapper.find(FilterableTable)).to.have.length(0);
-      expect(wrapper.find(Alert)).to.have.length(1);
-      expect(wrapper.find(Alert).shallow().text()).to.equal('The query returned no data');
+      expect(wrapper.find(FilterableTable)).toHaveLength(0);
+      expect(wrapper.find(Alert)).toHaveLength(1);
+      expect(wrapper.find(Alert).shallow().text()).toBe('The query returned no data');
     });
     it('should render cached query', () => {
       const wrapper = shallow(<ResultSet {...cachedQueryProps} />);
@@ -94,15 +93,15 @@ describe('ResultSet', () => {
       ];
       wrapper.setState({ data: cachedData });
       const filterableTable = wrapper.find(FilterableTable);
-      expect(filterableTable.props().data).to.equal(cachedData);
+      expect(filterableTable.props().data).toBe(cachedData);
     });
     it('should render stopped query', () => {
       const wrapper = shallow(<ResultSet {...stoppedQueryProps} />);
-      expect(wrapper.find(Alert)).to.have.length(1);
+      expect(wrapper.find(Alert)).toHaveLength(1);
     });
     it('should render running/pending/fetching query', () => {
       const wrapper = shallow(<ResultSet {...runningQueryProps} />);
-      expect(wrapper.find(ProgressBar)).to.have.length(1);
+      expect(wrapper.find(ProgressBar)).toHaveLength(1);
     });
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx b/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx
index c47f593..c0e194f 100644
--- a/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { FormControl } from 'react-bootstrap';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 import SaveQuery from '../../../src/SqlLab/components/SaveQuery';
 import ModalTrigger from '../../../src/components/ModalTrigger';
 
@@ -16,25 +15,25 @@ describe('SavedQuery', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<SaveQuery />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('is valid with props', () => {
     expect(
       React.isValidElement(<SaveQuery {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('has a ModalTrigger', () => {
     const wrapper = shallow(<SaveQuery {...mockedProps} />);
-    expect(wrapper.find(ModalTrigger)).to.have.length(1);
+    expect(wrapper.find(ModalTrigger)).toHaveLength(1);
   });
   it('has a cancel button', () => {
     const wrapper = shallow(<SaveQuery {...mockedProps} />);
     const modal = shallow(wrapper.instance().renderModalBody());
-    expect(modal.find('.cancelQuery')).to.have.length(1);
+    expect(modal.find('.cancelQuery')).toHaveLength(1);
   });
   it('has 2 FormControls', () => {
     const wrapper = shallow(<SaveQuery {...mockedProps} />);
     const modal = shallow(wrapper.instance().renderModalBody());
-    expect(modal.find(FormControl)).to.have.length(2);
+    expect(modal.find(FormControl)).toHaveLength(2);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
index cb6ebde..b5f0187 100644
--- a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import sinon from 'sinon';
-import { expect } from 'chai';
 
 import $ from 'jquery';
 import { table, defaultQueryEditor, databases, tables } from './fixtures';
@@ -36,10 +35,10 @@ describe('SqlEditorLeftBar', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<SqlEditorLeftBar {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders a TableElement', () => {
-    expect(wrapper.find(TableElement)).to.have.length(1);
+    expect(wrapper.find(TableElement)).toHaveLength(1);
   });
   describe('onDatabaseChange', () => {
     it('should fetch tables', () => {
@@ -47,21 +46,21 @@ describe('SqlEditorLeftBar', () => {
       sinon.stub(wrapper.instance(), 'fetchSchemas');
       wrapper.instance().onDatabaseChange({ value: 1, label: 'main' });
 
-      expect(wrapper.instance().fetchTables.getCall(0).args[0]).to.equal(1);
-      expect(wrapper.instance().fetchSchemas.getCall(0).args[0]).to.equal(1);
+      expect(wrapper.instance().fetchTables.getCall(0).args[0]).toBe(1);
+      expect(wrapper.instance().fetchSchemas.getCall(0).args[0]).toBe(1);
       wrapper.instance().fetchTables.restore();
       wrapper.instance().fetchSchemas.restore();
     });
     it('should clear tableOptions', () => {
       wrapper.instance().onDatabaseChange();
-      expect(wrapper.state().tableOptions).to.deep.equal([]);
+      expect(wrapper.state().tableOptions).toEqual([]);
     });
   });
   describe('getTableNamesBySubStr', () => {
     it('should handle empty', () => (
       wrapper.instance().getTableNamesBySubStr('')
         .then((data) => {
-          expect(data).to.deep.equal({ options: [] });
+          expect(data).toEqual({ options: [] });
         })
     ));
     it('should handle table name', () => {
@@ -80,14 +79,14 @@ describe('SqlEditorLeftBar', () => {
 
       return wrapper.instance().getTableNamesBySubStr('my table')
         .then((data) => {
-          expect(ajaxStub.getCall(0).args[0]).to.equal('/superset/tables/1/main/my table');
-          expect(data).to.deep.equal(mockTableOptions);
+          expect(ajaxStub.getCall(0).args[0]).toBe('/superset/tables/1/main/my table');
+          expect(data).toEqual(mockTableOptions);
         });
     });
   });
   it('dbMutator should build databases options', () => {
     const options = wrapper.instance().dbMutator(databases);
-    expect(options).to.deep.equal([
+    expect(options).toEqual([
       { value: 1, label: 'main' },
       { value: 208, label: 'Presto - Gold' },
     ]);
@@ -95,8 +94,8 @@ describe('SqlEditorLeftBar', () => {
   describe('fetchTables', () => {
     it('should clear table options', () => {
       wrapper.instance().fetchTables(1);
-      expect(wrapper.state().tableOptions).to.deep.equal([]);
-      expect(wrapper.state().filterOptions).to.be.a('null');
+      expect(wrapper.state().tableOptions).toEqual([]);
+      expect(wrapper.state().filterOptions).toBeNull();
     });
     it('should fetch table options', () => {
       ajaxStub.callsFake(() => {
@@ -106,8 +105,8 @@ describe('SqlEditorLeftBar', () => {
       });
       wrapper.instance().fetchTables(1, 'main', 'birth_names');
 
-      expect(ajaxStub.getCall(0).args[0]).to.equal('/superset/tables/1/main/birth_names/');
-      expect(wrapper.state().tableLength).to.equal(3);
+      expect(ajaxStub.getCall(0).args[0]).toBe('/superset/tables/1/main/birth_names/');
+      expect(wrapper.state().tableLength).toBe(3);
     });
     it('should handle error', () => {
       ajaxStub.callsFake(() => {
@@ -116,8 +115,8 @@ describe('SqlEditorLeftBar', () => {
         return d.promise();
       });
       wrapper.instance().fetchTables(1, 'main', 'birth_names');
-      expect(wrapper.state().tableOptions).to.deep.equal([]);
-      expect(wrapper.state().tableLength).to.equal(0);
+      expect(wrapper.state().tableOptions).toEqual([]);
+      expect(wrapper.state().tableLength).toBe(0);
     });
   });
   describe('fetchSchemas', () => {
@@ -131,8 +130,8 @@ describe('SqlEditorLeftBar', () => {
         return d.promise();
       });
       wrapper.instance().fetchSchemas(1);
-      expect(ajaxStub.getCall(0).args[0]).to.equal('/superset/schemas/1/false/');
-      expect(wrapper.state().schemaOptions).to.have.length(3);
+      expect(ajaxStub.getCall(0).args[0]).toBe('/superset/schemas/1/false/');
+      expect(wrapper.state().schemaOptions).toHaveLength(3);
     });
     it('should handle error', () => {
       ajaxStub.callsFake(() => {
@@ -141,7 +140,7 @@ describe('SqlEditorLeftBar', () => {
         return d.promise();
       });
       wrapper.instance().fetchSchemas(123);
-      expect(wrapper.state().schemaOptions).to.deep.equal([]);
+      expect(wrapper.state().schemaOptions).toEqual([]);
     });
   });
   describe('changeTable', () => {
@@ -156,23 +155,23 @@ describe('SqlEditorLeftBar', () => {
         value: 'birth_names',
         label: 'birth_names',
       });
-      expect(wrapper.state().tableName).to.equal('birth_names');
+      expect(wrapper.state().tableName).toBe('birth_names');
     });
     it('test 2', () => {
       wrapper.instance().changeTable({
         value: 'main.my_table',
         label: 'my_table',
       });
-      expect(wrapper.instance().fetchTables.getCall(0).args[1]).to.equal('main');
+      expect(wrapper.instance().fetchTables.getCall(0).args[1]).toBe('main');
     });
   });
   it('changeSchema', () => {
     sinon.stub(wrapper.instance(), 'fetchTables');
 
     wrapper.instance().changeSchema({ label: 'main', value: 'main' });
-    expect(wrapper.instance().fetchTables.getCall(0).args[1]).to.equal('main');
+    expect(wrapper.instance().fetchTables.getCall(0).args[1]).toBe('main');
     wrapper.instance().changeSchema();
-    expect(wrapper.instance().fetchTables.getCall(1).args[1]).to.be.a('null');
+    expect(wrapper.instance().fetchTables.getCall(1).args[1]).toBeNull();
 
     wrapper.instance().fetchTables.restore();
   });
diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx
index 4e6a2c8..822192d 100644
--- a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import { initialState, queries, table } from './fixtures';
 import SqlEditor from '../../../src/SqlLab/components/SqlEditor';
@@ -21,10 +20,10 @@ describe('SqlEditor', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<SqlEditor {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('render a SqlEditorLeftBar', () => {
     const wrapper = shallow(<SqlEditor {...mockedProps} />);
-    expect(wrapper.find(SqlEditorLeftBar)).to.have.length(1);
+    expect(wrapper.find(SqlEditorLeftBar)).toHaveLength(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx b/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx
index f959419..da98296 100644
--- a/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import sinon from 'sinon';
-import { expect } from 'chai';
 import { shallow } from 'enzyme';
 
 import TabStatusIcon from '../../../src/SqlLab/components/TabStatusIcon';
@@ -14,21 +13,21 @@ function setup() {
 describe('TabStatusIcon', () => {
   it('renders a circle without an x when hovered', () => {
     const { wrapper } = setup();
-    expect(wrapper.find('div.circle')).to.have.length(1);
-    expect(wrapper.text()).to.equal('');
+    expect(wrapper.find('div.circle')).toHaveLength(1);
+    expect(wrapper.text()).toBe('');
   });
 
   it('renders a circle with an x when hovered', () => {
     const { wrapper } = setup();
     wrapper.simulate('mouseOver');
-    expect(wrapper.find('div.circle')).to.have.length(1);
-    expect(wrapper.text()).to.equal('×');
+    expect(wrapper.find('div.circle')).toHaveLength(1);
+    expect(wrapper.text()).toBe('×');
   });
 
   it('calls onClose from props when clicked', () => {
     const { wrapper, onClose } = setup();
     wrapper.simulate('click');
     // eslint-disable-next-line no-unused-expressions
-    expect(onClose.calledOnce).to.be.true;
+    expect(onClose.calledOnce).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
index 6d4e007..046e2a6 100644
--- a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
@@ -5,7 +5,6 @@ import URI from 'urijs';
 
 import { Tab } from 'react-bootstrap';
 import { shallow, mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import { table, initialState } from './fixtures';
@@ -63,7 +62,7 @@ describe('TabbedSqlEditors', () => {
   it('is valid', () => {
     expect(
       React.isValidElement(<TabbedSqlEditors {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   describe('componentDidMount', () => {
     let uriStub;
@@ -82,27 +81,24 @@ describe('TabbedSqlEditors', () => {
       wrapper = mount(<TabbedSqlEditors {...mockedProps} />, {
         context: { store },
       });
-      expect(TabbedSqlEditors.prototype.componentDidMount.calledOnce).to.equal(true);
-      expect(window.history.replaceState.getCall(0).args[2])
-        .to.equal('/superset/sqllab');
+      expect(TabbedSqlEditors.prototype.componentDidMount.calledOnce).toBe(true);
+      expect(window.history.replaceState.getCall(0).args[2]).toBe('/superset/sqllab');
     });
     it('should handle savedQueryId', () => {
       uriStub.returns({ savedQueryId: 1 });
       wrapper = mount(<TabbedSqlEditors {...mockedProps} />, {
         context: { store },
       });
-      expect(TabbedSqlEditors.prototype.componentDidMount.calledOnce).to.equal(true);
-      expect(window.history.replaceState.getCall(0).args[2])
-        .to.equal('/superset/sqllab');
+      expect(TabbedSqlEditors.prototype.componentDidMount.calledOnce).toBe(true);
+      expect(window.history.replaceState.getCall(0).args[2]).toBe('/superset/sqllab');
     });
     it('should handle sql', () => {
       uriStub.returns({ sql: 1, dbid: 1 });
       wrapper = mount(<TabbedSqlEditors {...mockedProps} />, {
         context: { store },
       });
-      expect(TabbedSqlEditors.prototype.componentDidMount.calledOnce).to.equal(true);
-      expect(window.history.replaceState.getCall(0).args[2])
-        .to.equal('/superset/sqllab');
+      expect(TabbedSqlEditors.prototype.componentDidMount.calledOnce).toBe(true);
+      expect(window.history.replaceState.getCall(0).args[2]).toBe('/superset/sqllab');
     });
   });
   describe('componentWillReceiveProps', () => {
@@ -116,8 +112,8 @@ describe('TabbedSqlEditors', () => {
       spy.restore();
     });
     it('should update queriesArray and dataPreviewQueries', () => {
-      expect(wrapper.state().queriesArray.slice(-1)[0]).to.equal(queries['B1-VQU1zW']);
-      expect(wrapper.state().dataPreviewQueries.slice(-1)[0]).to.equal(queries['B1-VQU1zW']);
+      expect(wrapper.state().queriesArray.slice(-1)[0]).toBe(queries['B1-VQU1zW']);
+      expect(wrapper.state().dataPreviewQueries.slice(-1)[0]).toBe(queries['B1-VQU1zW']);
     });
   });
   it('should rename Tab', () => {
@@ -126,7 +122,7 @@ describe('TabbedSqlEditors', () => {
     sinon.stub(wrapper.instance().props.actions, 'queryEditorSetTitle');
 
     wrapper.instance().renameTab(queryEditors[0]);
-    expect(wrapper.instance().props.actions.queryEditorSetTitle.getCall(0).args[1]).to.equal('new title');
+    expect(wrapper.instance().props.actions.queryEditorSetTitle.getCall(0).args[1]).toBe('new title');
 
     delete global.prompt;
   });
@@ -136,7 +132,7 @@ describe('TabbedSqlEditors', () => {
 
     wrapper.instance().removeQueryEditor(queryEditors[0]);
     expect(wrapper.instance().props.actions.removeQueryEditor.getCall(0).args[0])
-      .to.equal(queryEditors[0]);
+        .toBe(queryEditors[0]);
   });
   it('should add new query editor', () => {
     wrapper = getWrapper();
@@ -144,7 +140,7 @@ describe('TabbedSqlEditors', () => {
 
     wrapper.instance().newQueryEditor();
     expect(wrapper.instance().props.actions.addQueryEditor.getCall(0).args[0].title)
-      .to.contain('Untitled Query');
+        .toContain('Untitled Query');
   });
   it('should handle select', () => {
     wrapper = getWrapper();
@@ -152,11 +148,11 @@ describe('TabbedSqlEditors', () => {
     sinon.stub(wrapper.instance().props.actions, 'setActiveQueryEditor');
 
     wrapper.instance().handleSelect('add_tab');
-    expect(wrapper.instance().newQueryEditor.callCount).to.equal(1);
+    expect(wrapper.instance().newQueryEditor.callCount).toBe(1);
 
     wrapper.instance().handleSelect('123');
     expect(wrapper.instance().props.actions.setActiveQueryEditor.getCall(0).args[0].id)
-      .to.contain(123);
+        .toContain(123);
     wrapper.instance().newQueryEditor.restore();
   });
   it('should render', () => {
@@ -164,10 +160,10 @@ describe('TabbedSqlEditors', () => {
     wrapper.setState({ hideLeftBar: true });
 
     const firstTab = wrapper.find(Tab).first();
-    expect(firstTab.props().eventKey).to.contain(initialState.sqlLab.queryEditors[0].id);
-    expect(firstTab.find(SqlEditor)).to.have.length(1);
+    expect(firstTab.props().eventKey).toContain(initialState.sqlLab.queryEditors[0].id);
+    expect(firstTab.find(SqlEditor)).toHaveLength(1);
 
     const lastTab = wrapper.find(Tab).last();
-    expect(lastTab.props().eventKey).to.contain('add_tab');
+    expect(lastTab.props().eventKey).toContain('add_tab');
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx b/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx
index 6d683d3..b1a3018 100644
--- a/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import { mount, shallow } from 'enzyme';
-import { expect } from 'chai';
 
 import Link from '../../../src/SqlLab/components/Link';
 import TableElement from '../../../src/SqlLab/components/TableElement';
@@ -16,43 +15,43 @@ describe('TableElement', () => {
   it('renders', () => {
     expect(
       React.isValidElement(<TableElement />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('renders with props', () => {
     expect(
       React.isValidElement(<TableElement {...mockedProps} />),
-    ).to.equal(true);
+    ).toBe(true);
   });
   it('has 2 Link elements', () => {
     const wrapper = shallow(<TableElement {...mockedProps} />);
-    expect(wrapper.find(Link)).to.have.length(2);
+    expect(wrapper.find(Link)).toHaveLength(2);
   });
   it('has 14 columns', () => {
     const wrapper = shallow(<TableElement {...mockedProps} />);
-    expect(wrapper.find(ColumnElement)).to.have.length(14);
+    expect(wrapper.find(ColumnElement)).toHaveLength(14);
   });
   it('mounts', () => {
     mount(<TableElement {...mockedProps} />);
   });
   it('sorts columns', () => {
     const wrapper = shallow(<TableElement {...mockedProps} />);
-    expect(wrapper.state().sortColumns).to.equal(false);
-    expect(wrapper.find(ColumnElement).first().props().column.name).to.equal('id');
+    expect(wrapper.state().sortColumns).toBe(false);
+    expect(wrapper.find(ColumnElement).first().props().column.name).toBe('id');
     wrapper.find('.sort-cols').simulate('click');
-    expect(wrapper.state().sortColumns).to.equal(true);
-    expect(wrapper.find(ColumnElement).first().props().column.name).to.equal('last_login');
+    expect(wrapper.state().sortColumns).toBe(true);
+    expect(wrapper.find(ColumnElement).first().props().column.name).toBe('last_login');
   });
   it('calls the collapseTable action', () => {
     const wrapper = mount(<TableElement {...mockedProps} />);
-    expect(mockedActions.collapseTable.called).to.equal(false);
+    expect(mockedActions.collapseTable.called).toBe(false);
     wrapper.find('.table-name').simulate('click');
-    expect(mockedActions.collapseTable.called).to.equal(true);
+    expect(mockedActions.collapseTable.called).toBe(true);
   });
   it('removes the table', () => {
     const wrapper = shallow(<TableElement {...mockedProps} />);
-    expect(wrapper.state().expanded).to.equal(true);
+    expect(wrapper.state().expanded).toBe(true);
     wrapper.find('.table-remove').simulate('click');
-    expect(wrapper.state().expanded).to.equal(false);
-    expect(mockedActions.removeDataPreview.called).to.equal(true);
+    expect(wrapper.state().expanded).toBe(false);
+    expect(mockedActions.removeDataPreview.called).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx b/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx
index c7a9534..fbabce4 100644
--- a/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx
@@ -1,12 +1,10 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { expect } from 'chai';
 import sinon from 'sinon';
 
 import Timer from '../../../src/components/Timer';
 import { now } from '../../../src/modules/dates';
 
-
 describe('Timer', () => {
   let wrapper;
   let clock;
@@ -26,30 +24,34 @@ describe('Timer', () => {
   });
 
   it('is a valid element', () => {
-    expect(React.isValidElement(<Timer {...mockedProps} />)).to.equal(true);
+    expect(React.isValidElement(<Timer {...mockedProps} />)).toBe(true);
   });
 
   it('componentWillMount starts timer after 30ms and sets state.clockStr', () => {
-    expect(wrapper.state().clockStr).to.equal('');
+    expect(wrapper.state().clockStr).toBe('');
     clock.tick(31);
-    expect(wrapper.state().clockStr).not.equal('');
+    expect(wrapper.state().clockStr).not.toBe('');
   });
 
   it('calls startTimer on mount', () => {
+    // Timer is already mounted in beforeEach
+    wrapper.unmount();
     const startTimerSpy = sinon.spy(Timer.prototype, 'startTimer');
     wrapper.mount();
-    expect(Timer.prototype.startTimer.calledOnce);
+    // Timer is started once in willUnmount and a second timer in render
+    // TODO: Questionable whether this is necessary.
+    expect(startTimerSpy.callCount).toBe(2);
     startTimerSpy.restore();
   });
 
   it('calls stopTimer on unmount', () => {
     const stopTimerSpy = sinon.spy(Timer.prototype, 'stopTimer');
     wrapper.unmount();
-    expect(Timer.prototype.stopTimer.calledOnce);
+    expect(stopTimerSpy.callCount).toBe(1);
     stopTimerSpy.restore();
   });
 
   it('renders a span with the correct class', () => {
-    expect(wrapper.find('span').hasClass('label-warning')).to.equal(true);
+    expect(wrapper.find('span').hasClass('label-warning')).toBe(true);
   });
 });
diff --git a/superset/assets/spec/javascripts/sqllab/actions_spec.js b/superset/assets/spec/javascripts/sqllab/actions_spec.js
index c2f1f45..ff5aaf6 100644
--- a/superset/assets/spec/javascripts/sqllab/actions_spec.js
+++ b/superset/assets/spec/javascripts/sqllab/actions_spec.js
@@ -1,5 +1,4 @@
 /* eslint-disable no-unused-expressions */
-import { expect } from 'chai';
 import sinon from 'sinon';
 import $ from 'jquery';
 import * as actions from '../../../src/SqlLab/actions';
... 2926 lines suppressed ...


Mime
View raw message