"""Test interact and interactive.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import print_function try: from unittest.mock import patch except ImportError: from mock import patch import nose.tools as nt from IPython.kernel.comm import Comm from jupyter_notebook import widgets from jupyter_notebook.widgets import interact, interactive, Widget, interaction from IPython.utils.py3compat import annotate #----------------------------------------------------------------------------- # Utility stuff #----------------------------------------------------------------------------- class DummyComm(Comm): comm_id = 'a-b-c-d' def open(self, *args, **kwargs): pass def send(self, *args, **kwargs): pass def close(self, *args, **kwargs): pass _widget_attrs = {} displayed = [] undefined = object() def setup(): _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined) Widget._comm_default = lambda self: DummyComm() _widget_attrs['_ipython_display_'] = Widget._ipython_display_ def raise_not_implemented(*args, **kwargs): raise NotImplementedError() Widget._ipython_display_ = raise_not_implemented def teardown(): for attr, value in _widget_attrs.items(): if value is undefined: delattr(Widget, attr) else: setattr(Widget, attr, value) def f(**kwargs): pass def clear_display(): global displayed displayed = [] def record_display(*args): displayed.extend(args) #----------------------------------------------------------------------------- # Actual tests #----------------------------------------------------------------------------- def check_widget(w, **d): """Check a single widget against a dict""" for attr, expected in d.items(): if attr == 'cls': nt.assert_is(w.__class__, expected) else: value = getattr(w, attr) nt.assert_equal(value, expected, "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected) ) def check_widgets(container, **to_check): """Check that widgets are created as expected""" # build a widget dictionary, so it matches widgets = {} for w in container.children: widgets[w.description] = w for key, d in to_check.items(): nt.assert_in(key, widgets) check_widget(widgets[key], **d) def test_single_value_string(): a = u'hello' c = interactive(f, a=a) w = c.children[0] check_widget(w, cls=widgets.Text, description='a', value=a, ) def test_single_value_bool(): for a in (True, False): c = interactive(f, a=a) w = c.children[0] check_widget(w, cls=widgets.Checkbox, description='a', value=a, ) def test_single_value_dict(): for d in [ dict(a=5), dict(a=5, b='b', c=dict), ]: c = interactive(f, d=d) w = c.children[0] check_widget(w, cls=widgets.Dropdown, description='d', options=d, value=next(iter(d.values())), ) def test_single_value_float(): for a in (2.25, 1.0, -3.5): c = interactive(f, a=a) w = c.children[0] check_widget(w, cls=widgets.FloatSlider, description='a', value=a, min= -a if a > 0 else 3*a, max= 3*a if a > 0 else -a, step=0.1, readout=True, ) def test_single_value_int(): for a in (1, 5, -3): c = interactive(f, a=a) nt.assert_equal(len(c.children), 1) w = c.children[0] check_widget(w, cls=widgets.IntSlider, description='a', value=a, min= -a if a > 0 else 3*a, max= 3*a if a > 0 else -a, step=1, readout=True, ) def test_list_tuple_2_int(): with nt.assert_raises(ValueError): c = interactive(f, tup=(1,1)) with nt.assert_raises(ValueError): c = interactive(f, tup=(1,-1)) for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]: c = interactive(f, tup=(min, max), lis=[min, max]) nt.assert_equal(len(c.children), 2) d = dict( cls=widgets.IntSlider, min=min, max=max, step=1, readout=True, ) check_widgets(c, tup=d, lis=d) def test_list_tuple_3_int(): with nt.assert_raises(ValueError): c = interactive(f, tup=(1,2,0)) with nt.assert_raises(ValueError): c = interactive(f, tup=(1,2,-1)) for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]: c = interactive(f, tup=(min, max, step), lis=[min, max, step]) nt.assert_equal(len(c.children), 2) d = dict( cls=widgets.IntSlider, min=min, max=max, step=step, readout=True, ) check_widgets(c, tup=d, lis=d) def test_list_tuple_2_float(): with nt.assert_raises(ValueError): c = interactive(f, tup=(1.0,1.0)) with nt.assert_raises(ValueError): c = interactive(f, tup=(0.5,-0.5)) for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]: c = interactive(f, tup=(min, max), lis=[min, max]) nt.assert_equal(len(c.children), 2) d = dict( cls=widgets.FloatSlider, min=min, max=max, step=.1, readout=True, ) check_widgets(c, tup=d, lis=d) def test_list_tuple_3_float(): with nt.assert_raises(ValueError): c = interactive(f, tup=(1,2,0.0)) with nt.assert_raises(ValueError): c = interactive(f, tup=(-1,-2,1.)) with nt.assert_raises(ValueError): c = interactive(f, tup=(1,2.,-1.)) for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]: c = interactive(f, tup=(min, max, step), lis=[min, max, step]) nt.assert_equal(len(c.children), 2) d = dict( cls=widgets.FloatSlider, min=min, max=max, step=step, readout=True, ) check_widgets(c, tup=d, lis=d) def test_list_tuple_str(): values = ['hello', 'there', 'guy'] first = values[0] c = interactive(f, tup=tuple(values), lis=list(values)) nt.assert_equal(len(c.children), 2) d = dict( cls=widgets.Dropdown, value=first, options=values ) check_widgets(c, tup=d, lis=d) def test_list_tuple_invalid(): for bad in [ (), (5, 'hi'), ('hi', 5), ({},), (None,), ]: with nt.assert_raises(ValueError): print(bad) # because there is no custom message in assert_raises c = interactive(f, tup=bad) def test_defaults(): @annotate(n=10) def f(n, f=4.5, g=1): pass c = interactive(f) check_widgets(c, n=dict( cls=widgets.IntSlider, value=10, ), f=dict( cls=widgets.FloatSlider, value=4.5, ), g=dict( cls=widgets.IntSlider, value=1, ), ) def test_default_values(): @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there']) def f(n, f=4.5, g=1, h=2, j='there'): pass c = interactive(f) check_widgets(c, n=dict( cls=widgets.IntSlider, value=10, ), f=dict( cls=widgets.FloatSlider, value=4.5, ), g=dict( cls=widgets.IntSlider, value=5, ), h=dict( cls=widgets.Dropdown, options={'a': 1, 'b': 2}, value=2 ), j=dict( cls=widgets.Dropdown, options=['hi', 'there'], value='there' ), ) def test_default_out_of_bounds(): @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there']) def f(f='hi', h=5, j='other'): pass c = interactive(f) check_widgets(c, f=dict( cls=widgets.FloatSlider, value=5., ), h=dict( cls=widgets.Dropdown, options={'a': 1}, value=1, ), j=dict( cls=widgets.Dropdown, options=['hi', 'there'], value='hi', ), ) def test_annotations(): @annotate(n=10, f=widgets.FloatText()) def f(n, f): pass c = interactive(f) check_widgets(c, n=dict( cls=widgets.IntSlider, value=10, ), f=dict( cls=widgets.FloatText, ), ) def test_priority(): @annotate(annotate='annotate', kwarg='annotate') def f(kwarg='default', annotate='default', default='default'): pass c = interactive(f, kwarg='kwarg') check_widgets(c, kwarg=dict( cls=widgets.Text, value='kwarg', ), annotate=dict( cls=widgets.Text, value='annotate', ), ) @nt.with_setup(clear_display) def test_decorator_kwarg(): with patch.object(interaction, 'display', record_display): @interact(a=5) def foo(a): pass nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, cls=widgets.IntSlider, value=5, ) @nt.with_setup(clear_display) def test_interact_instancemethod(): class Foo(object): def show(self, x): print(x) f = Foo() with patch.object(interaction, 'display', record_display): g = interact(f.show, x=(1,10)) nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, cls=widgets.IntSlider, value=5, ) @nt.with_setup(clear_display) def test_decorator_no_call(): with patch.object(interaction, 'display', record_display): @interact def foo(a='default'): pass nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, cls=widgets.Text, value='default', ) @nt.with_setup(clear_display) def test_call_interact(): def foo(a='default'): pass with patch.object(interaction, 'display', record_display): ifoo = interact(foo) nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, cls=widgets.Text, value='default', ) @nt.with_setup(clear_display) def test_call_interact_kwargs(): def foo(a='default'): pass with patch.object(interaction, 'display', record_display): ifoo = interact(foo, a=10) nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, cls=widgets.IntSlider, value=10, ) @nt.with_setup(clear_display) def test_call_decorated_on_trait_change(): """test calling @interact decorated functions""" d = {} with patch.object(interaction, 'display', record_display): @interact def foo(a='default'): d['a'] = a return a nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, cls=widgets.Text, value='default', ) # test calling the function directly a = foo('hello') nt.assert_equal(a, 'hello') nt.assert_equal(d['a'], 'hello') # test that setting trait values calls the function w.value = 'called' nt.assert_equal(d['a'], 'called') @nt.with_setup(clear_display) def test_call_decorated_kwargs_on_trait_change(): """test calling @interact(foo=bar) decorated functions""" d = {} with patch.object(interaction, 'display', record_display): @interact(a='kwarg') def foo(a='default'): d['a'] = a return a nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, cls=widgets.Text, value='kwarg', ) # test calling the function directly a = foo('hello') nt.assert_equal(a, 'hello') nt.assert_equal(d['a'], 'hello') # test that setting trait values calls the function w.value = 'called' nt.assert_equal(d['a'], 'called') def test_fixed(): c = interactive(f, a=widgets.fixed(5), b='text') nt.assert_equal(len(c.children), 1) w = c.children[0] check_widget(w, cls=widgets.Text, value='text', description='b', ) def test_default_description(): c = interactive(f, b='text') w = c.children[0] check_widget(w, cls=widgets.Text, value='text', description='b', ) def test_custom_description(): d = {} def record_kwargs(**kwargs): d.clear() d.update(kwargs) c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo')) w = c.children[0] check_widget(w, cls=widgets.Text, value='text', description='foo', ) w.value = 'different text' nt.assert_equal(d, {'b': 'different text'}) def test_interact_manual_button(): c = interactive(f, __manual=True) w = c.children[0] check_widget(w, cls=widgets.Button) def test_interact_manual_nocall(): callcount = 0 def calltest(testarg): callcount += 1 c = interactive(calltest, testarg=5, __manual=True) c.children[0].value = 10 nt.assert_equal(callcount, 0) def test_int_range_logic(): irsw = widgets.IntRangeSlider w = irsw(value=(2, 4), min=0, max=6) check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) w.value = (4, 2) check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) w.value = (-1, 7) check_widget(w, cls=irsw, value=(0, 6), min=0, max=6) w.min = 3 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6) w.max = 3 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3) w.min = 0 w.max = 6 w.lower = 2 w.upper = 4 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) w.value = (0, 1) #lower non-overlapping range check_widget(w, cls=irsw, value=(0, 1), min=0, max=6) w.value = (5, 6) #upper non-overlapping range check_widget(w, cls=irsw, value=(5, 6), min=0, max=6) w.value = (-1, 4) #semi out-of-range check_widget(w, cls=irsw, value=(0, 4), min=0, max=6) w.lower = 2 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) w.value = (-2, -1) #wholly out of range check_widget(w, cls=irsw, value=(0, 0), min=0, max=6) w.value = (7, 8) check_widget(w, cls=irsw, value=(6, 6), min=0, max=6) with nt.assert_raises(ValueError): w.min = 7 with nt.assert_raises(ValueError): w.max = -1 with nt.assert_raises(ValueError): w.lower = 5 with nt.assert_raises(ValueError): w.upper = 1 w = irsw(min=2, max=3) check_widget(w, min=2, max=3) w = irsw(min=100, max=200) check_widget(w, lower=125, upper=175, value=(125, 175)) with nt.assert_raises(ValueError): irsw(value=(2, 4), lower=3) with nt.assert_raises(ValueError): irsw(value=(2, 4), upper=3) with nt.assert_raises(ValueError): irsw(value=(2, 4), lower=3, upper=3) with nt.assert_raises(ValueError): irsw(min=2, max=1) with nt.assert_raises(ValueError): irsw(lower=5) with nt.assert_raises(ValueError): irsw(upper=5) def test_float_range_logic(): frsw = widgets.FloatRangeSlider w = frsw(value=(.2, .4), min=0., max=.6) check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) w.value = (.4, .2) check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) w.value = (-.1, .7) check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6) w.min = .3 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6) w.max = .3 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3) w.min = 0. w.max = .6 w.lower = .2 w.upper = .4 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) w.value = (0., .1) #lower non-overlapping range check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6) w.value = (.5, .6) #upper non-overlapping range check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6) w.value = (-.1, .4) #semi out-of-range check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6) w.lower = .2 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) w.value = (-.2, -.1) #wholly out of range check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6) w.value = (.7, .8) check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6) with nt.assert_raises(ValueError): w.min = .7 with nt.assert_raises(ValueError): w.max = -.1 with nt.assert_raises(ValueError): w.lower = .5 with nt.assert_raises(ValueError): w.upper = .1 w = frsw(min=2, max=3) check_widget(w, min=2, max=3) w = frsw(min=1., max=2.) check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75)) with nt.assert_raises(ValueError): frsw(value=(2, 4), lower=3) with nt.assert_raises(ValueError): frsw(value=(2, 4), upper=3) with nt.assert_raises(ValueError): frsw(value=(2, 4), lower=3, upper=3) with nt.assert_raises(ValueError): frsw(min=.2, max=.1) with nt.assert_raises(ValueError): frsw(lower=5) with nt.assert_raises(ValueError): frsw(upper=5) def test_multiple_selection(): smw = widgets.SelectMultiple # degenerate multiple select w = smw() check_widget(w, value=tuple(), options=None, selected_labels=tuple()) # don't accept random other value when no options with nt.assert_raises(KeyError): w.value = (2,) check_widget(w, value=tuple(), selected_labels=tuple()) # basic multiple select w = smw(options=[(1, 1)], value=[1]) check_widget(w, cls=smw, value=(1,), options=[(1, 1)]) # don't accept random other value with nt.assert_raises(KeyError): w.value = w.value + (2,) check_widget(w, value=(1,), selected_labels=(1,)) # change options w.options = w.options + [(2, 2)] check_widget(w, options=[(1, 1), (2,2)]) # change value w.value = w.value + (2,) check_widget(w, value=(1, 2), selected_labels=(1, 2)) # change value name w.selected_labels = (1,) check_widget(w, value=(1,)) # don't accept random other names when no options with nt.assert_raises(KeyError): w.selected_labels = (3,) check_widget(w, value=(1,)) # don't accept selected_label (from superclass) with nt.assert_raises(AttributeError): w.selected_label = 3 # don't return selected_label (from superclass) with nt.assert_raises(AttributeError): print(w.selected_label) # dict style w.options = {1: 1} check_widget(w, options={1: 1}) # updating with nt.assert_raises(KeyError): w.value = (2,) check_widget(w, options={1: 1})