diff --git a/caravel/assets/.babelrc b/caravel/assets/.babelrc
index ad8d1fe608e..338435d875a 100644
--- a/caravel/assets/.babelrc
+++ b/caravel/assets/.babelrc
@@ -1,3 +1,3 @@
{
- "presets" : ["es2015", "react"]
+ "presets" : ["airbnb", "es2015", "react"]
}
diff --git a/caravel/assets/javascripts/explore/components/QueryAndSaveBtns.jsx b/caravel/assets/javascripts/explore/components/QueryAndSaveBtns.jsx
index 120c7fdcd10..f878163564a 100644
--- a/caravel/assets/javascripts/explore/components/QueryAndSaveBtns.jsx
+++ b/caravel/assets/javascripts/explore/components/QueryAndSaveBtns.jsx
@@ -6,28 +6,25 @@ const propTypes = {
onQuery: PropTypes.func.isRequired,
};
-export default class QueryAndSaveBtns extends React.Component {
- render() {
- const saveClasses = classnames('btn btn-default', {
- 'disabled disabledButton': this.props.canAdd !== 'True',
- });
+export default function QueryAndSaveBtns({ canAdd, onQuery }) {
+ const saveClasses = classnames('btn btn-default', {
+ 'disabled disabledButton': canAdd !== 'True',
+ });
- return (
-
-
-
-
- );
- }
+ return (
+
+
+
+
+ );
}
QueryAndSaveBtns.propTypes = propTypes;
diff --git a/caravel/assets/js_build.sh b/caravel/assets/js_build.sh
index 3efd138bc57..98f9696bf47 100755
--- a/caravel/assets/js_build.sh
+++ b/caravel/assets/js_build.sh
@@ -4,4 +4,5 @@ cd "$(dirname "$0")"
npm --version
npm install
npm run lint
+npm run test
npm run prod
diff --git a/caravel/assets/package.json b/caravel/assets/package.json
index 1f798176714..d257998cf82 100644
--- a/caravel/assets/package.json
+++ b/caravel/assets/package.json
@@ -1,13 +1,13 @@
{
"name": "caravel",
"version": "0.1.0",
- "description": "Any database to any visualization",
+ "description": "Caravel is a data exploration platform designed to be visual, intuitive, and interactive.",
"directories": {
"doc": "docs",
- "test": "tests"
+ "test": "spec"
},
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "test": "mocha --compilers js:babel-core/register --required spec/helpers/browser.js spec/**/*_spec.*",
"dev": "webpack -d --watch --colors",
"prod": "webpack -p --colors",
"lint": "npm run --silent lint:js",
@@ -36,10 +36,6 @@
"homepage": "https://github.com/airbnb/caravel#readme",
"dependencies": {
"autobind-decorator": "^1.3.3",
- "babel-loader": "^6.2.1",
- "babel-polyfill": "^6.3.14",
- "babel-preset-es2015": "^6.3.13",
- "babel-preset-react": "^6.3.13",
"bootstrap": "^3.3.6",
"bootstrap-datepicker": "^1.6.0",
"bootstrap-toggle": "^2.2.1",
@@ -47,7 +43,6 @@
"brfs": "^1.4.3",
"cal-heatmap": "3.5.4",
"classnames": "^2.2.5",
- "css-loader": "^0.23.1",
"d3": "^3.5.14",
"d3-cloud": "^1.2.1",
"d3-sankey": "^0.2.1",
@@ -55,20 +50,15 @@
"datamaps": "^0.4.4",
"datatables-bootstrap3-plugin": "^0.4.0",
"datatables.net-bs": "^1.10.11",
- "exports-loader": "^0.6.3",
"font-awesome": "^4.5.0",
"gridster": "^0.5.6",
"immutability-helper": "^2.0.0",
- "imports-loader": "^0.6.5",
"jquery": "^2.2.1",
- "jquery-ui": "^1.10.5",
- "json-loader": "^0.5.4",
- "less": "^2.6.1",
- "less-loader": "^2.2.2",
+ "jquery-ui": "1.10.5",
"mapbox-gl": "^0.20.0",
"mustache": "^2.2.1",
"nvd3": "1.8.3",
- "react": "^15.2.0",
+ "react": "^15.2.1",
"react-bootstrap": "^0.28.3",
"react-bootstrap-table": "^2.3.7",
"react-dom": "^0.14.8",
@@ -77,17 +67,35 @@
"react-resizable": "^1.3.3",
"select2": "3.5",
"select2-bootstrap-css": "^1.4.6",
- "style-loader": "^0.13.0",
"supercluster": "https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40",
"topojson": "^1.6.22",
- "transform-loader": "^0.2.3",
- "viewport-mercator-project": "^2.1.0",
- "webpack": "^1.12.12",
- "webworkify-webpack": "1.0.6"
+ "viewport-mercator-project": "^2.1.0"
},
"devDependencies": {
+ "babel": "^6.3.26",
+ "babel-core": "^6.10.4",
+ "babel-loader": "^6.2.4",
+ "babel-preset-airbnb": "^1.1.1",
+ "babel-preset-es2015": "^6.9.0",
+ "babel-preset-react": "^6.11.1",
+ "chai": "^3.5.0",
+ "css-loader": "^0.23.1",
+ "enzyme": "^2.0.0",
"eslint": "^2.2.0",
+ "exports-loader": "^0.6.3",
"file-loader": "^0.8.5",
- "url-loader": "^0.5.7"
+ "imports-loader": "^0.6.5",
+ "jsdom": "^8.0.1",
+ "json-loader": "^0.5.4",
+ "less": "^2.6.1",
+ "less-loader": "^2.2.2",
+ "mocha": "^2.4.5",
+ "react-addons-test-utils": "^0.14.8",
+ "react-dom": "^0.14.8",
+ "style-loader": "^0.13.0",
+ "transform-loader": "^0.2.3",
+ "url-loader": "^0.5.7",
+ "webpack": "^1.13.1",
+ "webworkify-webpack": "1.0.6"
}
}
diff --git a/caravel/assets/spec/helpers/browser.js b/caravel/assets/spec/helpers/browser.js
new file mode 100644
index 00000000000..b847b83a1f3
--- /dev/null
+++ b/caravel/assets/spec/helpers/browser.js
@@ -0,0 +1,22 @@
+/* eslint no-undef: 0, no-native-reassign: 0 */
+
+require('babel-register')();
+
+var jsdom = require('jsdom').jsdom;
+
+var exposedProperties = ['window', 'navigator', 'document'];
+
+global.document = jsdom('');
+global.window = document.defaultView;
+Object.keys(document.defaultView).forEach((property) => {
+ if (typeof global[property] === 'undefined') {
+ exposedProperties.push(property);
+ global[property] = document.defaultView[property];
+ }
+});
+
+global.navigator = {
+ userAgent: 'node.js'
+};
+
+documentRef = document;
diff --git a/caravel/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx b/caravel/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx
new file mode 100644
index 00000000000..e4872f8ddf1
--- /dev/null
+++ b/caravel/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+
+import QueryAndSaveButtons from '../../../../javascripts/explore/components/QueryAndSaveBtns';
+
+describe('QueryAndSaveButtons', () => {
+ let defaultProps = {
+ canAdd: 'True',
+ onQuery: () => {}
+ };
+
+ // It must render
+ it('renders', () => {
+ expect(React.isValidElement()).to.equal(true);
+ });
+
+ // Test the output
+ describe('output', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallow();
+ });
+
+ it('renders 2 buttons', () => {
+ expect(wrapper.find('button')).to.have.lengthOf(2);
+ });
+
+ it('renders buttons with correct text', () => {
+ expect(wrapper.find('button').contains('Query')).to.eql(true);
+ expect(wrapper.find('button').contains('Save as')).to.eql(true);
+ });
+ });
+});
diff --git a/caravel/assets/webpack.config.js b/caravel/assets/webpack.config.js
index fa713f5e9fe..c8650d28654 100644
--- a/caravel/assets/webpack.config.js
+++ b/caravel/assets/webpack.config.js
@@ -1,9 +1,9 @@
-var path = require('path');
-var APP_DIR = path.resolve(__dirname, './'); // input
-var BUILD_DIR = path.resolve(__dirname, './javascripts/dist'); // output
+const path = require('path');
-var config = {
- // for now generate one compiled js file per entry point / html page
+const APP_DIR = path.resolve(__dirname, './'); // input
+const BUILD_DIR = path.resolve(__dirname, './javascripts/dist'); // output
+
+const config = {
entry: {
'css-theme': APP_DIR + '/javascripts/css-theme.js',
dashboard: APP_DIR + '/javascripts/dashboard/Dashboard.jsx',
@@ -18,6 +18,7 @@ var config = {
filename: '[name].entry.js'
},
resolve: {
+ extensions: ['', '.js', '.jsx'],
alias: {
webworkify: 'webworkify-webpack'
}
@@ -25,41 +26,50 @@ var config = {
module: {
loaders: [
{
- test: /\.jsx?/,
- include: APP_DIR,
+ test: /\.jsx?$/,
exclude: APP_DIR + '/node_modules',
- loader: 'babel'
+ loader: 'babel',
+ query: {
+ presets: ['react', 'es2015', 'stage-0']
+ }
},
- /* for react-map-gl overlays */
+
+ /* for react-map-gl overlays */
{
test: /\.react\.js$/,
include: APP_DIR + '/node_modules/react-map-gl/src/overlays',
loader: 'babel'
},
- /* for require('*.css') */
+
+ /* for require('*.css') */
{
test: /\.css$/,
include: APP_DIR,
loader: "style-loader!css-loader"
},
- /* for css linking images */
+
+ /* for css linking images */
{ test: /\.png$/, loader: "url-loader?limit=100000" },
{ test: /\.jpg$/, loader: "file-loader" },
{ test: /\.gif$/, loader: "file-loader" },
- /* for font-awesome */
+
+ /* for font-awesome */
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" },
- /* for require('*.less') */
+
+ /* for require('*.less') */
{
test: /\.less$/,
include: APP_DIR,
loader: "style!css!less"
},
- /* for mapbox */
+
+ /* for mapbox */
{
test: /\.json$/,
loader: 'json-loader'
- }, {
+ },
+ {
test: /\.js$/,
include: APP_DIR + '/node_modules/mapbox-gl/js/render/painter/use_program.js',
loader: 'transform/cacheable?brfs'
@@ -71,6 +81,11 @@ var config = {
query: 'brfs'
}]
},
+ externals: {
+ cheerio: 'window',
+ 'react/lib/ExecutionEnvironment': true,
+ 'react/lib/ReactContext': true
+ },
plugins: []
};