Skip to content

Commit 8fdd412

Browse files
authored
Merge pull request #549 from plotly/test-jupyter
Add JS tests for Jupyter NBs
2 parents 4191458 + 7da7ed2 commit 8fdd412

File tree

13 files changed

+455
-6
lines changed

13 files changed

+455
-6
lines changed

circle.yml

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ machine:
88
PLOTLY_TOX_PYTHON_33: /home/ubuntu/.pyenv/versions/3.3.3/bin/python3.3
99
PLOTLY_TOX_PYTHON_34: /home/ubuntu/.pyenv/versions/3.4.3/bin/python3.4
1010
PLOTLY_TOX_PYTHON_35: /home/ubuntu/.pyenv/versions/3.5.0/bin/python3.5
11+
PLOTLY_JUPYTER_TEST_DIR: /home/ubuntu/${CIRCLE_PROJECT_REPONAME}/plotly/tests/test_optional/test_jupyter
12+
13+
node:
14+
# use a pre-installed version of node so we don't need to download it.
15+
version: 4.2.2
1116

1217
dependencies:
1318

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

29+
# install jupyter test JS requirements
30+
- cd ${PLOTLY_JUPYTER_TEST_DIR} && npm i
31+
2432
cache_directories:
2533

2634
# cache everything that tox installs for us.
2735
- .tox
36+
- ${PLOTLY_JUPYTER_TEST_DIR}/node_modules
2837

2938
test:
3039

optional-requirements.txt

+7-3
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@ numpy
1212
# matplotlib==1.3.1
1313

1414
## testing dependencies ##
15-
nose==1.3.3
15+
nose
16+
coverage
1617

17-
## ipython dependencies ##
18-
ipython[all]==3.0.0
18+
## ipython ##
19+
ipython
1920

2021
## pandas deps for some matplotlib functionality ##
2122
pandas
2223

2324
## scipy deps for some FigureFactory functions ##
2425
scipy
2526

27+
## jupyter ##
28+
jupyter
29+
ipykernel

plotly/tests/test_core/test_image/test_image.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import tempfile
66
import os
77
import itertools
8+
import warnings
89

910
from nose.plugins.attrib import attr
1011

12+
from plotly import exceptions
1113
from plotly.plotly import plotly as py
1214

1315

@@ -24,9 +26,20 @@ def setUp(self):
2426
def _generate_image_get_returns_valid_image_test(image_format,
2527
width, height, scale):
2628
def test(self):
27-
image = py.image.get(self.data, image_format, width, height, scale)
28-
if image_format in ['png', 'jpeg']:
29-
assert imghdr.what('', image) == image_format
29+
# TODO: better understand why this intermittently fails. See #649
30+
num_attempts = 5
31+
for i in range(num_attempts):
32+
if i > 0:
33+
warnings.warn('image test intermittently failed, retrying...')
34+
try:
35+
image = py.image.get(self.data, image_format, width, height,
36+
scale)
37+
if image_format in ['png', 'jpeg']:
38+
assert imghdr.what('', image) == image_format
39+
return
40+
except (KeyError, exceptions.PlotlyError):
41+
if i == num_attempts - 1:
42+
raise
3043

3144
return test
3245

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
fixtures/*.html
3+
!fixtures/*.ipynb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {
7+
"collapsed": false
8+
},
9+
"outputs": [],
10+
"source": [
11+
"from plotly.offline import plot, iplot, init_notebook_mode\n",
12+
"import plotly.graph_objs as go\n",
13+
"\n",
14+
"# Make plotly work with Jupyter notebook\n",
15+
"init_notebook_mode()\n",
16+
"\n",
17+
"keys=['one','two','three']\n",
18+
"values=[1,2,3]\n",
19+
"\n",
20+
"iplot({\n",
21+
" \"data\": [go.Bar(x=keys, y=values)],\n",
22+
" \"layout\": go.Layout(title=\"Sample Bar Chart\")\n",
23+
"})"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": null,
29+
"metadata": {
30+
"collapsed": true
31+
},
32+
"outputs": [],
33+
"source": []
34+
}
35+
],
36+
"metadata": {
37+
"kernelspec": {
38+
"display_name": "Python 3",
39+
"language": "python",
40+
"name": "python3"
41+
},
42+
"language_info": {
43+
"codemirror_mode": {
44+
"name": "ipython",
45+
"version": 3
46+
},
47+
"file_extension": ".py",
48+
"mimetype": "text/x-python",
49+
"name": "python",
50+
"nbconvert_exporter": "python",
51+
"pygments_lexer": "ipython3",
52+
"version": "3.5.2"
53+
}
54+
},
55+
"nbformat": 4,
56+
"nbformat_minor": 1
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {
7+
"collapsed": false
8+
},
9+
"outputs": [],
10+
"source": [
11+
"from plotly.offline import plot, iplot, init_notebook_mode\n",
12+
"import plotly.graph_objs as go\n",
13+
"\n",
14+
"# Make plotly work with Jupyter notebook\n",
15+
"init_notebook_mode(connected=True)\n",
16+
"\n",
17+
"keys=['one','two','three']\n",
18+
"values=[1,2,3]\n",
19+
"\n",
20+
"iplot({\n",
21+
" \"data\": [go.Bar(x=keys, y=values)],\n",
22+
" \"layout\": go.Layout(title=\"Sample Bar Chart\")\n",
23+
"})"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": null,
29+
"metadata": {
30+
"collapsed": true
31+
},
32+
"outputs": [],
33+
"source": []
34+
}
35+
],
36+
"metadata": {
37+
"kernelspec": {
38+
"display_name": "Python 3",
39+
"language": "python",
40+
"name": "python3"
41+
},
42+
"language_info": {
43+
"codemirror_mode": {
44+
"name": "ipython",
45+
"version": 3
46+
},
47+
"file_extension": ".py",
48+
"mimetype": "text/x-python",
49+
"name": "python",
50+
"nbconvert_exporter": "python",
51+
"pygments_lexer": "ipython3",
52+
"version": "3.5.2"
53+
}
54+
},
55+
"nbformat": 4,
56+
"nbformat_minor": 1
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
var test = require('../lib/tape-wrapper');
4+
5+
test('should load plotly.js', function(t) {
6+
t.plan(1);
7+
8+
window.require(['plotly'], function(Plotly) {
9+
t.equal(typeof Plotly, 'object');
10+
});
11+
});
12+
13+
test('should have one plotly.js graph', function(t) {
14+
t.plan(1);
15+
16+
var nodes = document.querySelectorAll('.js-plotly-plot');
17+
t.equal(nodes.length, 1);
18+
});
19+
20+
test('should inject raw plotly.js code into DOM', function(t) {
21+
t.plan(1);
22+
23+
var nodes = document.querySelectorAll('script');
24+
nodes = Array.prototype.slice.call(nodes, 0, 10);
25+
26+
var results = nodes.filter(function(node) {
27+
return node.innerHTML.substr(0, 19) === 'if(!window.Plotly){';
28+
});
29+
30+
t.equal(results.length, 1);
31+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
var test = require('../lib/tape-wrapper');
4+
5+
test('should load plotly.js', function(t) {
6+
t.plan(1);
7+
8+
window.require(['plotly'], function(Plotly) {
9+
t.equal(typeof Plotly, 'object');
10+
});
11+
});
12+
13+
test('should have one plotly.js graph', function(t) {
14+
t.plan(1);
15+
16+
var nodes = document.querySelectorAll('.js-plotly-plot');
17+
t.equal(nodes.length, 1);
18+
});
19+
20+
test('should link to plotly.js CDN', function(t) {
21+
t.plan(1);
22+
23+
var nodes = document.querySelectorAll('script');
24+
nodes = Array.prototype.slice.call(nodes, 0);
25+
26+
var results = nodes.filter(function(node) {
27+
return node.src === 'https://cdn.plot.ly/plotly-latest.min.js';
28+
});
29+
30+
t.equal(results.length, 1);
31+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
var http = require('http');
2+
var url = require('url');
3+
var fs = require('fs');
4+
var path = require('path');
5+
6+
var ecstatic = require('ecstatic');
7+
var browserify = require('browserify');
8+
var cheerio = require('cheerio');
9+
var tapParser = require('tap-parser');
10+
var chrome = require('chrome-launch');
11+
12+
var PORT = 8080;
13+
var PATH_ROOT = path.join(__dirname, '..');
14+
var PATH_INDEX_STUB = path.join(PATH_ROOT, 'index.tmp.html');
15+
var PATH_TEST_BUNDLE = path.join(PATH_ROOT, 'test.tmp.js');
16+
17+
var URL = 'http://localhost:' + PORT + '/index.tmp.html';
18+
var EXIT_CODE = 0;
19+
20+
if(process.argv.length !== 4) {
21+
throw new Error('must provide path to html and js files');
22+
}
23+
24+
var PATH_INDEX = process.argv[2];
25+
var PATH_TEST_FILE = process.argv[3];
26+
27+
main();
28+
29+
function main() {
30+
scanInput();
31+
32+
stubIndex()
33+
.then(bundleTests)
34+
.then(startServer)
35+
.then(launch);
36+
}
37+
38+
function scanInput() {
39+
var reqFiles = [PATH_INDEX, PATH_TEST_FILE];
40+
41+
reqFiles.forEach(function(filePath) {
42+
if(!doesFileExist(filePath)) {
43+
throw new Error(filePath + ' does not exist');
44+
}
45+
});
46+
}
47+
48+
function stubIndex() {
49+
return new Promise(function(resolve, reject) {
50+
var html = fs.readFileSync(PATH_INDEX, 'utf-8');
51+
var $ = cheerio.load(html);
52+
53+
$('body').append('<script type="text/javascript" src="../test.tmp.js"></script>');
54+
55+
fs.writeFile(PATH_INDEX_STUB, $.html(), resolve);
56+
});
57+
}
58+
59+
function bundleTests() {
60+
return new Promise(function(resolve, reject) {
61+
var wsBundle = fs.createWriteStream(PATH_TEST_BUNDLE);
62+
63+
browserify(PATH_TEST_FILE, { debug: true })
64+
.bundle()
65+
.pipe(wsBundle);
66+
67+
wsBundle.on('close', resolve);
68+
});
69+
}
70+
71+
function startServer() {
72+
return new Promise(function(resolve, reject) {
73+
var server = http.createServer(ecstatic({ root: PATH_ROOT }));
74+
75+
server.on('request', handle);
76+
77+
server.listen(PORT, resolve);
78+
});
79+
}
80+
81+
function handle(req, res) {
82+
var query = url.parse(req.url).query || '';
83+
var parser = tapParser();
84+
85+
function is(query, root) {
86+
return query.indexOf(root) !== -1;
87+
}
88+
89+
if(is(query, 'data')) handleData(req, res);
90+
if(is(query, 'done')) handleDone();
91+
92+
function handleData(req, res) {
93+
req.pipe(parser);
94+
req.pipe(process.stdout);
95+
}
96+
97+
parser.on('assert', function(assert) {
98+
if(EXIT_CODE === 0 && assert.ok === false) EXIT_CODE = 1;
99+
})
100+
101+
function handleDone() {
102+
removeBuildFiles();
103+
process.exit(EXIT_CODE);
104+
}
105+
}
106+
107+
function launch() {
108+
chrome(URL);
109+
}
110+
111+
function removeBuildFiles() {
112+
fs.unlinkSync(PATH_INDEX_STUB);
113+
fs.unlinkSync(PATH_TEST_BUNDLE);
114+
}
115+
116+
function doesFileExist(filePath) {
117+
try {
118+
if(fs.statSync(filePath).isFile()) return true;
119+
}
120+
catch(e) {
121+
return false;
122+
}
123+
124+
return false;
125+
}

0 commit comments

Comments
 (0)