import unittest.mock from functools import partial import bokeh.core.properties as bp import param import pytest from bokeh.document import Document from bokeh.io.doc import patch_curdoc from bokeh.models import Div from panel.layout import Tabs, WidgetBox from panel.reactive import Reactive, ReactiveHTML from panel.viewable import Viewable from panel.widgets import Checkbox, StaticText, TextInput, IntInput def test_reactive_default_title(): doc = ReactiveHTML().server_doc() assert doc.title == 'Panel Application' def test_reactive_servable_title(): doc = Document() session_context = unittest.mock.Mock() with patch_curdoc(doc): doc._session_context = lambda: session_context ReactiveHTML().servable(title='A') ReactiveHTML().servable(title='B') assert doc.title == 'B' def test_link(): "Link two Reactive objects" class ReactiveLink(Reactive): a = param.Parameter() obj = ReactiveLink() obj2 = ReactiveLink() obj.link(obj2, a='a') obj.a = 1 assert obj.a == 1 assert obj2.a == 1 def test_param_rename(): "Test that Reactive renames params and properties" class ReactiveRename(Reactive): a = param.Parameter() _rename = {'a': 'b'} obj = ReactiveRename() params = obj._process_property_change({'b': 1}) assert params == {'a': 1} properties = obj._process_param_change({'a': 1}) assert properties == {'b': 1} def test_link_properties_nb(document, comm): class ReactiveLink(Reactive): text = param.String(default='A') obj = ReactiveLink() div = Div() # Link property and check bokeh js property callback is defined obj._link_props(div, ['text'], document, div, comm) assert 'text' in div._callbacks # Assert callback is set up correctly cb = div._callbacks['text'][0] assert isinstance(cb, partial) assert cb.args == (document, div.ref['id'], comm, None) assert cb.func == obj._comm_change def test_link_properties_server(document): class ReactiveLink(Reactive): text = param.String(default='A') obj = ReactiveLink() div = Div() # Link property and check bokeh callback is defined obj._link_props(div, ['text'], document, div) assert 'text' in div._callbacks # Assert callback is set up correctly cb = div._callbacks['text'][0] assert isinstance(cb, partial) assert cb.args == (document, div.ref['id'], None) assert cb.func == obj._server_change def test_text_input_controls(): text_input = TextInput() controls = text_input.controls() assert isinstance(controls, Tabs) assert len(controls) == 2 wb1, wb2 = controls assert isinstance(wb1, WidgetBox) assert len(wb1) == 6 name, disabled, *(ws) = wb1 assert isinstance(name, StaticText) assert isinstance(disabled, Checkbox) not_checked = [] for w in ws: if w.name == 'Value': assert isinstance(w, TextInput) text_input.value = "New value" assert w.value == "New value" elif w.name == 'Value input': assert isinstance(w, TextInput) elif w.name == 'Placeholder': assert isinstance(w, TextInput) text_input.placeholder = "Test placeholder..." assert w.value == "Test placeholder..." elif w.name == 'Max length': assert isinstance(w, IntInput) else: not_checked.append(w) assert not not_checked assert isinstance(wb2, WidgetBox) assert len(wb2) == len(list(Viewable.param)) + 1 def test_text_input_controls_explicit(): text_input = TextInput() controls = text_input.controls(['placeholder', 'disabled']) assert isinstance(controls, WidgetBox) assert len(controls) == 3 name, disabled, placeholder = controls assert isinstance(name, StaticText) assert isinstance(disabled, Checkbox) assert isinstance(placeholder, TextInput) text_input.disabled = True assert disabled.value text_input.placeholder = "Test placeholder..." assert placeholder.value == "Test placeholder..." def test_reactive_html_basic(): class Test(ReactiveHTML): int = param.Integer(default=3, doc='An integer') float = param.Number(default=3.14, doc='A float') _template = '
' data_model = Test._data_model assert data_model.__name__ == 'Test1' properties = data_model.properties() assert 'int' in properties assert 'float' in properties int_prop = data_model.lookup('int') assert isinstance(int_prop.property, bp.Int) assert int_prop.class_default(data_model) == 3 float_prop = data_model.lookup('float') assert isinstance(float_prop.property, bp.Float) assert float_prop.class_default(data_model) == 3.14 assert Test._node_callbacks == {} test = Test() root = test.get_root() assert test._attrs == {'div': [('width', ['int'], '{int}')]} assert root.callbacks == {} assert root.events == {} def test_reactive_html_no_id_param_error(): with pytest.raises(ValueError) as excinfo: class Test(ReactiveHTML): width = param.Number(default=200) _template = '
' assert "Found
node with the `width` attribute referencing the `width` parameter." in str(excinfo.value) def test_reactive_html_no_id_method_error(): with pytest.raises(ValueError) as excinfo: class Test(ReactiveHTML): _template = '
' def _onclick(self): pass assert "Found
node with the `onclick` callback referencing the `_onclick` method." in str(excinfo.value) def test_reactive_html_dom_events(): class TestDOMEvents(ReactiveHTML): int = param.Integer(default=3, doc='An integer') float = param.Number(default=3.14, doc='A float') _template = '
' _dom_events = {'div': ['change']} data_model = TestDOMEvents._data_model assert data_model.__name__ == 'TestDOMEvents1' properties = data_model.properties() assert 'int' in properties assert 'float' in properties int_prop = data_model.lookup('int') assert isinstance(int_prop.property, bp.Int) assert int_prop.class_default(data_model) == 3 float_prop = data_model.lookup('float') assert isinstance(float_prop.property, bp.Float) assert float_prop.class_default(data_model) == 3.14 assert TestDOMEvents._node_callbacks == {} test = TestDOMEvents() root = test.get_root() assert test._attrs == {'div': [('width', ['int'], '{int}')]} assert root.callbacks == {} assert root.events == {'div': {'change': True}} def test_reactive_html_inline(): class TestInline(ReactiveHTML): int = param.Integer(default=3, doc='An integer') _template = '
' def _div_change(self, event): pass data_model = TestInline._data_model assert data_model.__name__ == 'TestInline1' properties = data_model.properties() assert 'int' in properties int_prop = data_model.lookup('int') assert isinstance(int_prop.property, bp.Int) assert int_prop.class_default(data_model) == 3 assert TestInline._node_callbacks == {'div': [('onchange', '_div_change')]} assert TestInline._inline_callbacks == [('div', 'onchange', '_div_change')] test = TestInline() root = test.get_root() assert test._attrs == { 'div': [ ('onchange', [], '{_div_change}'), ('width', ['int'], '{int}') ] } assert root.callbacks == {'div': [('onchange', '_div_change')]} assert root.events == {} test.on_event('div', 'click', print) assert root.events == {'div': {'click': False}} def test_reactive_html_children(): class TestChildren(ReactiveHTML): children = param.List(default=[]) _template = '
${children}
' assert TestChildren._node_callbacks == {} assert TestChildren._inline_callbacks == [] assert TestChildren._parser.children == {'div': 'children'} widget = TextInput() test = TestChildren(children=[widget]) root = test.get_root() assert test._attrs == {} assert root.children == {'div': [widget._models[root.ref['id']][0]]} assert len(widget._models) == 1 assert test._panes == {'children': [widget]} widget_new = TextInput() test.children = [widget_new] assert len(widget._models) == 0 assert root.children == {'div': [widget_new._models[root.ref['id']][0]]} assert test._panes == {'children': [widget_new]} test._cleanup(root) assert len(test._models) == 0 assert len(widget_new._models) == 0 def test_reactive_html_templated_children(): class TestTemplatedChildren(ReactiveHTML): children = param.List(default=[]) _template = """ {% for key, option in children.items() %} {% endfor %}
""" assert TestTemplatedChildren._node_callbacks == {} assert TestTemplatedChildren._inline_callbacks == [] assert TestTemplatedChildren._parser.children == {'option': 'children'} widget = TextInput() test = TestTemplatedChildren(children={'test': widget}) root = test.get_root() assert test._attrs == {} assert root.looped == ['option'] assert root.children == {'option': [widget._models[root.ref['id']][0]]} assert test._panes == {'children': [widget]} widget_model = widget._models[root.ref['id']][0] widget_new = TextInput() test.children = {'test': widget_new, 'test2': widget} assert len(widget._models) == 1 assert root.children == { 'option': [ widget_new._models[root.ref['id']][0], widget_model ] } assert test._panes == {'children': [widget_new, widget]} def test_reactive_html_templated_children_add_loop_id(): class TestTemplatedChildren(ReactiveHTML): children = param.List(default=[]) _template = """ """ assert TestTemplatedChildren._node_callbacks == {} assert TestTemplatedChildren._inline_callbacks == [] assert TestTemplatedChildren._parser.children == {'option': 'children'} test = TestTemplatedChildren(children=['A', 'B', 'C']) assert test._get_template()[0] == """ """ model = test.get_root() assert test._attrs == {} assert model.looped == ['option'] def test_reactive_html_templated_children_add_loop_id_and_for_loop_var(): class TestTemplatedChildren(ReactiveHTML): children = param.List(default=[]) _template = """ """ assert TestTemplatedChildren._node_callbacks == {} assert TestTemplatedChildren._inline_callbacks == [] assert TestTemplatedChildren._parser.children == {'option': 'children'} test = TestTemplatedChildren(children=['A', 'B', 'C']) assert test._get_template()[0] == """ """ model = test.get_root() assert test._attrs == {} assert model.looped == ['option'] @pytest.mark.parametrize('operator', ['', '+', '-', '*', '\\', '%', '**', '>>', '<<', '>>>', '&', '^', '&&', '||', '??']) @pytest.mark.parametrize('sep', [' ', '']) def test_reactive_html_scripts_linked_properties_assignment_operator(operator, sep): class TestScripts(ReactiveHTML): clicks = param.Integer() _template = "
" _scripts = {'render': f'test.onclick = () => {{ data.clicks{sep}{operator}= 1 }}'} assert TestScripts()._linked_properties() == ['clicks']