#----------------------------------------------------------------------------- # Copyright (c) 2012 - 2017, Anaconda, Inc. All rights reserved. # # Powered by the Bokeh Development Team. # # The full license is in the file LICENSE.txt, distributed with this software. #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Boilerplate #----------------------------------------------------------------------------- from __future__ import absolute_import, division, print_function, unicode_literals import pytest ; pytest #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Standard library imports from mock import patch # External imports import bs4 from jinja2 import Template from six import string_types # Bokeh imports from bokeh.core.properties import Instance from bokeh.document import Document from bokeh.io import curdoc from bokeh.model import Model from bokeh.plotting import figure from bokeh.resources import CDN, JSResources, CSSResources from bokeh.util.string import encode_utf8 # Module under test import bokeh.embed.standalone as bes from bokeh.embed.util import RenderRoot #----------------------------------------------------------------------------- # Setup #----------------------------------------------------------------------------- class SomeModelInTestObjects(Model): child = Instance(Model) def stable_id(): return 'ID' @pytest.fixture def test_plot(): from bokeh.plotting import figure test_plot = figure() test_plot.circle([1, 2], [2, 3]) return test_plot #----------------------------------------------------------------------------- # General API #----------------------------------------------------------------------------- class Test_autoload_static(object): def test_return_type(self, test_plot): r = bes.autoload_static(test_plot, CDN, "some/path") assert len(r) == 2 @patch('bokeh.embed.util.make_id', new_callable=lambda: stable_id) def test_script_attrs(self, mock_make_id, test_plot): js, tag = bes.autoload_static(test_plot, CDN, "some/path") html = bs4.BeautifulSoup(tag, "lxml") scripts = html.findAll(name='script') assert len(scripts) == 1 attrs = scripts[0].attrs assert set(attrs) == set(['src', 'id']) assert attrs['src'] == 'some/path' class Test_components(object): def test_return_type(self): plot1 = figure() plot1.circle([], []) plot2 = figure() plot2.circle([], []) # This is a testing artefact, users dont' have to do this in practice curdoc().add_root(plot1) curdoc().add_root(plot2) r = bes.components(plot1) assert len(r) == 2 _, divs = bes.components((plot1, plot2)) assert isinstance(divs, tuple) _, divs = bes.components([plot1, plot2]) assert isinstance(divs, tuple) _, divs = bes.components({"Plot 1": plot1, "Plot 2": plot2}) assert isinstance(divs, dict) assert all(isinstance(x, string_types) for x in divs.keys()) @patch('bokeh.embed.util.make_id', new_callable=lambda: stable_id) def test_plot_dict_returned_when_wrap_plot_info_is_false(self, mock_make_id): doc = Document() plot1 = figure() plot1.circle([], []) doc.add_root(plot1) plot2 = figure() plot2.circle([], []) doc.add_root(plot2) expected_plotdict_1 = RenderRoot(elementid="ID", id="ID") expected_plotdict_2 = RenderRoot(elementid="ID", id="ID") _, plotdict = bes.components(plot1, wrap_plot_info=False) assert plotdict == expected_plotdict_1 _, plotids = bes.components((plot1, plot2), wrap_plot_info=False) assert plotids == (expected_plotdict_1, expected_plotdict_2) _, plotiddict = bes.components({'p1': plot1, 'p2': plot2}, wrap_plot_info=False) assert plotiddict == {'p1': expected_plotdict_1, 'p2': expected_plotdict_2} def test_result_attrs(self, test_plot): script, div = bes.components(test_plot) html = bs4.BeautifulSoup(script, "lxml") scripts = html.findAll(name='script') assert len(scripts) == 1 assert scripts[0].attrs == {'type': 'text/javascript'} def test_div_attrs(self, test_plot): script, div = bes.components(test_plot) html = bs4.BeautifulSoup(div, "lxml") divs = html.findAll(name='div') assert len(divs) == 1 div = divs[0] assert set(div.attrs) == set(['class', 'id']) assert div.attrs['class'] == ['bk-root'] assert div.text == '' def test_script_is_utf8_encoded(self, test_plot): script, div = bes.components(test_plot) assert isinstance(script, str) @patch('bokeh.embed.util.make_id', new_callable=lambda: stable_id) def test_output_is_without_script_tag_when_wrap_script_is_false(self, mock_make_id, test_plot): script, div = bes.components(test_plot) html = bs4.BeautifulSoup(script, "lxml") scripts = html.findAll(name='script') assert len(scripts) == 1 # XXX: this needs to account for indentation #script_content = scripts[0].getText() #rawscript, div = bes.components(test_plot, wrap_script=False) #self.maxDiff = None #assert rawscript.strip() == script_content.strip() class Test_file_html(object): def test_return_type(self, test_plot): class fake_template: def __init__(self, tester, user_template_variables=None): self.tester = tester self.template_variables = { "title", "bokeh_js", "bokeh_css", "plot_script", "doc", "docs", "base", } if user_template_variables is not None: self.template_variables.update(user_template_variables) def render(self, template_variables): assert self.template_variables.issubset(set(template_variables.keys())) return "template result" r = bes.file_html(test_plot, CDN, "title") assert isinstance(r, str) r = bes.file_html(test_plot, CDN, "title", fake_template(self)) assert isinstance(r, str) r = bes.file_html(test_plot, CDN, "title", fake_template(self, {"test_var"}), {"test_var": "test"}) assert isinstance(r, str) @patch('bokeh.embed.bundle.warn') def test_file_html_handles_js_only_resources(self, mock_warn, test_plot): js_resources = JSResources(mode="relative", components=["bokeh"]) template = Template("{{ bokeh_js }}") output = bes.file_html(test_plot, (js_resources, None), "title", template=template) html = encode_utf8("%s" % js_resources.render_js()) assert output == html @patch('bokeh.embed.bundle.warn') def test_file_html_provides_warning_if_no_css(self, mock_warn, test_plot): js_resources = JSResources() bes.file_html(test_plot, (js_resources, None), "title") mock_warn.assert_called_once_with( 'No Bokeh CSS Resources provided to template. If required you will need to provide them manually.' ) @patch('bokeh.embed.bundle.warn') def test_file_html_handles_css_only_resources(self, mock_warn, test_plot): css_resources = CSSResources(mode="relative", components=["bokeh"]) template = Template("{{ bokeh_css }}") output = bes.file_html(test_plot, (None, css_resources), "title", template=template) html = encode_utf8("%s" % css_resources.render_css()) assert output == html @patch('bokeh.embed.bundle.warn') def test_file_html_provides_warning_if_no_js(self, mock_warn, test_plot): css_resources = CSSResources() bes.file_html(test_plot, (None, css_resources), "title") mock_warn.assert_called_once_with( 'No Bokeh JS Resources provided to template. If required you will need to provide them manually.' ) def test_file_html_title_is_escaped(self, test_plot): r = bes.file_html(test_plot, CDN, "&<") assert "&<" in r def test_entire_doc_is_used(self): from bokeh.document import Document from bokeh.models import Button fig = figure() fig.x([0], [0]) button = Button(label="Button") d = Document() d.add_root(fig) d.add_root(button) out = bes.file_html([fig], CDN) # this is a very coarse test but it will do assert "bokeh-widgets" in out #----------------------------------------------------------------------------- # Dev API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- class Test__ModelInDocument(object): def test_single_model(self): p = Model() assert p.document is None with bes._ModelInDocument([p]): assert p.document is not None assert p.document is None def test_list_of_model(self): p1 = Model() p2 = Model() assert p1.document is None assert p2.document is None with bes._ModelInDocument([p1, p2]): assert p1.document is not None assert p2.document is not None assert p1.document is None assert p2.document is None def test_uses_precedent(self): # it's deliberate that the doc is on p2, so _ModelInDocument # has to be smart about looking for a doc anywhere in the list # before it starts inventing new documents doc = Document() p1 = Model() p2 = Model() doc.add_root(p2) assert p1.document is None assert p2.document is not None with bes._ModelInDocument([p1, p2]): assert p1.document is not None assert p2.document is not None assert p1.document is doc assert p2.document is doc assert p1.document is None assert p2.document is not None def test_uses_doc_precedent(self): doc = Document() p1 = Model() p2 = Model() assert p1.document is None assert p2.document is None with bes._ModelInDocument([p1, p2, doc]): assert p1.document is not None assert p2.document is not None assert p1.document is doc assert p2.document is doc assert p1.document is None assert p2.document is None def test_with_doc_in_child_raises_error(self): doc = Document() p1 = Model() p2 = SomeModelInTestObjects(child=Model()) doc.add_root(p2.child) assert p1.document is None assert p2.document is None assert p2.child.document is doc with pytest.raises(RuntimeError): with bes._ModelInDocument([p1, p2]): assert p1.document is not None assert p2.document is not None assert p1.document is doc assert p2.document is doc @patch('bokeh.document.document.check_integrity') def test_validates_document_by_default(self, check_integrity, test_plot): with bes._ModelInDocument([test_plot]): pass assert check_integrity.called @patch('bokeh.document.document.check_integrity') def test_doesnt_validate_doc_due_to_env_var(self, check_integrity, monkeypatch, test_plot): monkeypatch.setenv("BOKEH_VALIDATE_DOC", "false") with bes._ModelInDocument([test_plot]): pass assert not check_integrity.called class Test__add_doc_to_models(object): pass class Test__title_from_models(object): pass