206 lines
6.7 KiB
Python
206 lines
6.7 KiB
Python
|
|
||
|
import json
|
||
|
import os
|
||
|
import re
|
||
|
import tempfile
|
||
|
import shutil
|
||
|
|
||
|
import traitlets
|
||
|
|
||
|
from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization
|
||
|
from ..embed import embed_data, embed_snippet, embed_minimal_html, dependency_state
|
||
|
|
||
|
try:
|
||
|
from io import StringIO
|
||
|
except ImportError:
|
||
|
from StringIO import StringIO
|
||
|
|
||
|
try:
|
||
|
# Python 3
|
||
|
from html.parser import HTMLParser
|
||
|
except ImportError:
|
||
|
# Python 2
|
||
|
from HTMLParser import HTMLParser
|
||
|
|
||
|
class CaseWidget(Widget):
|
||
|
"""Widget to test dependency traversal"""
|
||
|
|
||
|
a = traitlets.Instance(Widget, allow_none=True).tag(sync=True, **widget_serialization)
|
||
|
b = traitlets.Instance(Widget, allow_none=True).tag(sync=True, **widget_serialization)
|
||
|
|
||
|
_model_name = traitlets.Unicode('CaseWidgetModel').tag(sync=True)
|
||
|
|
||
|
other = traitlets.Dict().tag(sync=True, **widget_serialization)
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
class TestEmbed:
|
||
|
|
||
|
def teardown(self):
|
||
|
for w in tuple(Widget.widgets.values()):
|
||
|
w.close()
|
||
|
|
||
|
def test_embed_data_simple(self):
|
||
|
w = IntText(4)
|
||
|
state = dependency_state(w, drop_defaults=True)
|
||
|
data = embed_data(views=w, drop_defaults=True, state=state)
|
||
|
|
||
|
state = data['manager_state']['state']
|
||
|
views = data['view_specs']
|
||
|
|
||
|
assert len(state) == 3
|
||
|
assert len(views) == 1
|
||
|
|
||
|
model_names = [s['model_name'] for s in state.values()]
|
||
|
assert 'IntTextModel' in model_names
|
||
|
|
||
|
def test_cors(self):
|
||
|
w = IntText(4)
|
||
|
code = embed_snippet(w)
|
||
|
# 1 is from the require
|
||
|
assert len(re.findall(' crossorigin', code)) > 1
|
||
|
f = StringIO()
|
||
|
embed_minimal_html(f, w)
|
||
|
assert len(re.findall(' crossorigin', f.getvalue())) > 1
|
||
|
|
||
|
code = embed_snippet(w, cors=False, requirejs=False)
|
||
|
assert ' crossorigin' not in code
|
||
|
f = StringIO()
|
||
|
embed_minimal_html(f, w, cors=False, requirejs=False)
|
||
|
assert ' crossorigin' not in f.getvalue()
|
||
|
|
||
|
code = embed_snippet(w, cors=False, requirejs=True)
|
||
|
assert len(re.findall(' crossorigin', code)) == 1 # 1 is from the require, which is ok
|
||
|
f = StringIO()
|
||
|
embed_minimal_html(f, w, cors=False, requirejs=True)
|
||
|
assert len(re.findall(' crossorigin', f.getvalue())) == 1 # 1 is from the require, which is ok
|
||
|
|
||
|
def test_escape(self):
|
||
|
w = Text('<script A> <ScRipt> </Script> <!-- --> <b>hi</b>')
|
||
|
code = embed_snippet(w)
|
||
|
assert code.find(r'<script A>') == -1
|
||
|
assert code.find(r'\u003cscript A> \u003cScRipt> \u003c/Script> \u003c!-- --> <b>hi</b>') >= 0
|
||
|
|
||
|
f = StringIO()
|
||
|
embed_minimal_html(f, w)
|
||
|
content = f.getvalue()
|
||
|
assert content.find(r'<script A>') == -1
|
||
|
assert content.find(r'\u003cscript A> \u003cScRipt> \u003c/Script> \u003c!-- --> <b>hi</b>') >= 0
|
||
|
|
||
|
def test_embed_data_two_widgets(self):
|
||
|
w1 = IntText(4)
|
||
|
w2 = IntSlider(min=0, max=100)
|
||
|
jslink((w1, 'value'), (w2, 'value'))
|
||
|
state = dependency_state([w1, w2], drop_defaults=True)
|
||
|
data = embed_data(views=[w1, w2], drop_defaults=True, state=state)
|
||
|
|
||
|
state = data['manager_state']['state']
|
||
|
views = data['view_specs']
|
||
|
|
||
|
assert len(state) == 7
|
||
|
assert len(views) == 2
|
||
|
|
||
|
model_names = [s['model_name'] for s in state.values()]
|
||
|
assert 'IntTextModel' in model_names
|
||
|
assert 'IntSliderModel' in model_names
|
||
|
|
||
|
def test_embed_data_complex(self):
|
||
|
w1 = IntText(4)
|
||
|
w2 = IntSlider(min=0, max=100)
|
||
|
jslink((w1, 'value'), (w2, 'value'))
|
||
|
|
||
|
w3 = CaseWidget()
|
||
|
w3.a = w1
|
||
|
|
||
|
w4 = CaseWidget()
|
||
|
w4.a = w3
|
||
|
w4.other['test'] = w2
|
||
|
|
||
|
# Add a circular reference:
|
||
|
w3.b = w4
|
||
|
|
||
|
# Put it in an HBox
|
||
|
HBox(children=[w4])
|
||
|
|
||
|
state = dependency_state(w3)
|
||
|
|
||
|
assert len(state) == 9
|
||
|
|
||
|
model_names = [s['model_name'] for s in state.values()]
|
||
|
assert 'IntTextModel' in model_names
|
||
|
assert 'IntSliderModel' in model_names
|
||
|
assert 'CaseWidgetModel' in model_names
|
||
|
assert 'LinkModel' in model_names
|
||
|
|
||
|
# Check that HBox is not collected
|
||
|
assert 'HBoxModel' not in model_names
|
||
|
|
||
|
# Check that views make sense:
|
||
|
|
||
|
data = embed_data(views=w3, drop_defaults=True, state=state)
|
||
|
assert state is data['manager_state']['state']
|
||
|
views = data['view_specs']
|
||
|
assert len(views) == 1
|
||
|
|
||
|
|
||
|
def test_snippet(self):
|
||
|
|
||
|
class Parser(HTMLParser):
|
||
|
state = 'initial'
|
||
|
states = []
|
||
|
|
||
|
def handle_starttag(self, tag, attrs):
|
||
|
attrs = dict(attrs)
|
||
|
if tag == 'script' and attrs.get('type', '') == "application/vnd.jupyter.widget-state+json":
|
||
|
self.state = 'widget-state'
|
||
|
self.states.append(self.state)
|
||
|
elif tag == 'script' and attrs.get('type', '') == "application/vnd.jupyter.widget-view+json":
|
||
|
self.state = 'widget-view'
|
||
|
self.states.append(self.state)
|
||
|
|
||
|
def handle_endtag(self, tag):
|
||
|
self.state = 'initial'
|
||
|
|
||
|
def handle_data(self, data):
|
||
|
if self.state == 'widget-state':
|
||
|
manager_state = json.loads(data)['state']
|
||
|
assert len(manager_state) == 3
|
||
|
self.states.append('check-widget-state')
|
||
|
elif self.state == 'widget-view':
|
||
|
view = json.loads(data)
|
||
|
assert isinstance(view, dict)
|
||
|
self.states.append('check-widget-view')
|
||
|
|
||
|
w = IntText(4)
|
||
|
state = dependency_state(w, drop_defaults=True)
|
||
|
snippet = embed_snippet(views=w, drop_defaults=True, state=state)
|
||
|
parser = Parser()
|
||
|
parser.feed(snippet)
|
||
|
print(parser.states)
|
||
|
assert parser.states == ['widget-state', 'check-widget-state', 'widget-view', 'check-widget-view']
|
||
|
|
||
|
def test_minimal_html_filename(self):
|
||
|
w = IntText(4)
|
||
|
|
||
|
tmpd = tempfile.mkdtemp()
|
||
|
|
||
|
try:
|
||
|
output = os.path.join(tmpd, 'test.html')
|
||
|
state = dependency_state(w, drop_defaults=True)
|
||
|
embed_minimal_html(output, views=w, drop_defaults=True, state=state)
|
||
|
# Check that the file is written to the intended destination:
|
||
|
with open(output, 'r') as f:
|
||
|
content = f.read()
|
||
|
assert content.splitlines()[0] == '<!DOCTYPE html>'
|
||
|
finally:
|
||
|
shutil.rmtree(tmpd)
|
||
|
|
||
|
def test_minimal_html_filehandle(self):
|
||
|
w = IntText(4)
|
||
|
output = StringIO()
|
||
|
state = dependency_state(w, drop_defaults=True)
|
||
|
embed_minimal_html(output, views=w, drop_defaults=True, state=state)
|
||
|
content = output.getvalue()
|
||
|
assert content.splitlines()[0] == '<!DOCTYPE html>'
|