Skip to content

Add JS tests for Jupyter NBs #549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 5, 2017
9 changes: 9 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ machine:
PLOTLY_TOX_PYTHON_33: /home/ubuntu/.pyenv/versions/3.3.3/bin/python3.3
PLOTLY_TOX_PYTHON_34: /home/ubuntu/.pyenv/versions/3.4.3/bin/python3.4
PLOTLY_TOX_PYTHON_35: /home/ubuntu/.pyenv/versions/3.5.0/bin/python3.5
PLOTLY_JUPYTER_TEST_DIR: /home/ubuntu/${CIRCLE_PROJECT_REPONAME}/plotly/tests/test_optional/test_jupyter

node:
# use a pre-installed version of node so we don't need to download it.
version: 4.2.2

dependencies:

Expand All @@ -21,10 +26,14 @@ dependencies:
# we need to cd out of the project root to ensure the install worked
- cd ~ && python -c "import plotly"

# install jupyter test JS requirements
- cd ${PLOTLY_JUPYTER_TEST_DIR} && npm i

cache_directories:

# cache everything that tox installs for us.
- .tox
- ${PLOTLY_JUPYTER_TEST_DIR}/node_modules

test:

Expand Down
10 changes: 7 additions & 3 deletions optional-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ numpy
# matplotlib==1.3.1

## testing dependencies ##
nose==1.3.3
nose
coverage

## ipython dependencies ##
ipython[all]==3.0.0
## ipython ##
ipython
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theengineear this didn't go down so smooth on Circle.


## pandas deps for some matplotlib functionality ##
pandas

## scipy deps for some FigureFactory functions ##
scipy

## jupyter ##
jupyter
ipykernel
19 changes: 16 additions & 3 deletions plotly/tests/test_core/test_image/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import tempfile
import os
import itertools
import warnings

from nose.plugins.attrib import attr

from plotly import exceptions
from plotly.plotly import plotly as py


Expand All @@ -24,9 +26,20 @@ def setUp(self):
def _generate_image_get_returns_valid_image_test(image_format,
width, height, scale):
def test(self):
image = py.image.get(self.data, image_format, width, height, scale)
if image_format in ['png', 'jpeg']:
assert imghdr.what('', image) == image_format
# TODO: better understand why this intermittently fails. See #649
num_attempts = 5
for i in range(num_attempts):
if i > 0:
warnings.warn('image test intermittently failed, retrying...')
try:
image = py.image.get(self.data, image_format, width, height,
scale)
if image_format in ['png', 'jpeg']:
assert imghdr.what('', image) == image_format
return
except (KeyError, exceptions.PlotlyError):
if i == num_attempts - 1:
raise

return test

Expand Down
3 changes: 3 additions & 0 deletions plotly/tests/test_optional/test_jupyter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
fixtures/*.html
!fixtures/*.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from plotly.offline import plot, iplot, init_notebook_mode\n",
"import plotly.graph_objs as go\n",
"\n",
"# Make plotly work with Jupyter notebook\n",
"init_notebook_mode()\n",
"\n",
"keys=['one','two','three']\n",
"values=[1,2,3]\n",
"\n",
"iplot({\n",
" \"data\": [go.Bar(x=keys, y=values)],\n",
" \"layout\": go.Layout(title=\"Sample Bar Chart\")\n",
"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from plotly.offline import plot, iplot, init_notebook_mode\n",
"import plotly.graph_objs as go\n",
"\n",
"# Make plotly work with Jupyter notebook\n",
"init_notebook_mode(connected=True)\n",
"\n",
"keys=['one','two','three']\n",
"values=[1,2,3]\n",
"\n",
"iplot({\n",
" \"data\": [go.Bar(x=keys, y=values)],\n",
" \"layout\": go.Layout(title=\"Sample Bar Chart\")\n",
"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

var test = require('../lib/tape-wrapper');

test('should load plotly.js', function(t) {
t.plan(1);

window.require(['plotly'], function(Plotly) {
t.equal(typeof Plotly, 'object');
});
});

test('should have one plotly.js graph', function(t) {
t.plan(1);

var nodes = document.querySelectorAll('.js-plotly-plot');
t.equal(nodes.length, 1);
});

test('should inject raw plotly.js code into DOM', function(t) {
t.plan(1);

var nodes = document.querySelectorAll('script');
nodes = Array.prototype.slice.call(nodes, 0, 10);

var results = nodes.filter(function(node) {
return node.innerHTML.substr(0, 19) === 'if(!window.Plotly){';
});

t.equal(results.length, 1);
});
31 changes: 31 additions & 0 deletions plotly/tests/test_optional/test_jupyter/js_tests/connected_true.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

var test = require('../lib/tape-wrapper');

test('should load plotly.js', function(t) {
t.plan(1);

window.require(['plotly'], function(Plotly) {
t.equal(typeof Plotly, 'object');
});
});

test('should have one plotly.js graph', function(t) {
t.plan(1);

var nodes = document.querySelectorAll('.js-plotly-plot');
t.equal(nodes.length, 1);
});

test('should link to plotly.js CDN', function(t) {
t.plan(1);

var nodes = document.querySelectorAll('script');
nodes = Array.prototype.slice.call(nodes, 0);

var results = nodes.filter(function(node) {
return node.src === 'https://cdn.plot.ly/plotly-latest.min.js';
});

t.equal(results.length, 1);
});
125 changes: 125 additions & 0 deletions plotly/tests/test_optional/test_jupyter/lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');

var ecstatic = require('ecstatic');
var browserify = require('browserify');
var cheerio = require('cheerio');
var tapParser = require('tap-parser');
var chrome = require('chrome-launch');

var PORT = 8080;
var PATH_ROOT = path.join(__dirname, '..');
var PATH_INDEX_STUB = path.join(PATH_ROOT, 'index.tmp.html');
var PATH_TEST_BUNDLE = path.join(PATH_ROOT, 'test.tmp.js');

var URL = 'http://localhost:' + PORT + '/index.tmp.html';
var EXIT_CODE = 0;

if(process.argv.length !== 4) {
throw new Error('must provide path to html and js files');
}

var PATH_INDEX = process.argv[2];
var PATH_TEST_FILE = process.argv[3];

main();

function main() {
scanInput();

stubIndex()
.then(bundleTests)
.then(startServer)
.then(launch);
}

function scanInput() {
var reqFiles = [PATH_INDEX, PATH_TEST_FILE];

reqFiles.forEach(function(filePath) {
if(!doesFileExist(filePath)) {
throw new Error(filePath + ' does not exist');
}
});
}

function stubIndex() {
return new Promise(function(resolve, reject) {
var html = fs.readFileSync(PATH_INDEX, 'utf-8');
var $ = cheerio.load(html);

$('body').append('<script type="text/javascript" src="../test.tmp.js"></script>');

fs.writeFile(PATH_INDEX_STUB, $.html(), resolve);
});
}

function bundleTests() {
return new Promise(function(resolve, reject) {
var wsBundle = fs.createWriteStream(PATH_TEST_BUNDLE);

browserify(PATH_TEST_FILE, { debug: true })
.bundle()
.pipe(wsBundle);

wsBundle.on('close', resolve);
});
}

function startServer() {
return new Promise(function(resolve, reject) {
var server = http.createServer(ecstatic({ root: PATH_ROOT }));

server.on('request', handle);

server.listen(PORT, resolve);
});
}

function handle(req, res) {
var query = url.parse(req.url).query || '';
var parser = tapParser();

function is(query, root) {
return query.indexOf(root) !== -1;
}

if(is(query, 'data')) handleData(req, res);
if(is(query, 'done')) handleDone();

function handleData(req, res) {
req.pipe(parser);
req.pipe(process.stdout);
}

parser.on('assert', function(assert) {
if(EXIT_CODE === 0 && assert.ok === false) EXIT_CODE = 1;
})

function handleDone() {
removeBuildFiles();
process.exit(EXIT_CODE);
}
}

function launch() {
chrome(URL);
}

function removeBuildFiles() {
fs.unlinkSync(PATH_INDEX_STUB);
fs.unlinkSync(PATH_TEST_BUNDLE);
}

function doesFileExist(filePath) {
try {
if(fs.statSync(filePath).isFile()) return true;
}
catch(e) {
return false;
}

return false;
}
Loading