diff --git a/IPython/html/widgets/tests/test_interaction.py b/IPython/html/widgets/tests/test_interaction.py index d2aab8a..ffe9316 100644 --- a/IPython/html/widgets/tests/test_interaction.py +++ b/IPython/html/widgets/tests/test_interaction.py @@ -11,12 +11,392 @@ # Imports #----------------------------------------------------------------------------- +from __future__ import print_function + +from collections import OrderedDict + import nose.tools as nt +import IPython.testing.tools as tt + +# from IPython.core.getipython import get_ipython +from IPython.html import widgets +from IPython.html.widgets import interact, interactive, Widget, interaction +from IPython.utils.py3compat import annotate +# from IPython.utils.capture import capture_output + +#----------------------------------------------------------------------------- +# Utility stuff +#----------------------------------------------------------------------------- + +class DummyComm(object): + comm_id = 'a-b-c-d' + def send(self, *args, **kwargs): + pass + + def close(self, *args, **kwargs): + pass + +_widget_attrs = {} +displayed = [] + +def setup(): + _widget_attrs['comm'] = Widget.comm + Widget.comm = DummyComm() + _widget_attrs['_ipython_display_'] = Widget._ipython_display_ + def raise_not_implemented(*args, **kwargs): + raise NotImplementedError() + Widget._ipython_display_ = raise_not_implemented -from IPython.html.widgets import interact, interactive -from IPython.html.widgets import interaction +def teardown(): + for attr, value in _widget_attrs.items(): + setattr(Widget, attr, value) + +def f(**kwargs): + pass + +def clear_display(): + global displayed + displayed = [] + +def record_display(*args): + displayed.extend(args) #----------------------------------------------------------------------------- -# Test functions +# 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.TextWidget, + 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.CheckboxWidget, + 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.DropdownWidget, + description='d', + values=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.FloatSliderWidget, + description='a', + value=a, + min= -a if a > 0 else 3*a, + max= 3*a if a > 0 else -a, + step=0.1, + ) + +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.IntSliderWidget, + description='a', + value=a, + min= -a if a > 0 else 3*a, + max= 3*a if a > 0 else -a, + step=1, + ) + +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.IntSliderWidget, + min=min, + max=max, + step=1, + ) + 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.IntSliderWidget, + min=min, + max=max, + step=step, + ) + 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.FloatSliderWidget, + min=min, + max=max, + step=.1, + ) + 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.FloatSliderWidget, + min=min, + max=max, + step=step, + ) + check_widgets(c, tup=d, lis=d) + +def test_list_tuple_str(): + values = ['hello', 'there', 'guy'] + first = values[0] + dvalues = OrderedDict((v,v) for v in values) + c = interactive(f, tup=tuple(values), lis=list(values)) + nt.assert_equal(len(c.children), 2) + d = dict( + cls=widgets.DropdownWidget, + value=first, + values=dvalues + ) + 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): + pass + + c = interactive(f) + check_widgets(c, + n=dict( + cls=widgets.IntSliderWidget, + value=10, + ), + f=dict( + cls=widgets.FloatSliderWidget, + value=4.5, + ), + ) + +def test_annotations(): + @annotate(n=10, f=widgets.FloatTextWidget()) + def f(n, f): + pass + + c = interactive(f) + check_widgets(c, + n=dict( + cls=widgets.IntSliderWidget, + value=10, + ), + f=dict( + cls=widgets.FloatTextWidget, + ), + ) + +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.TextWidget, + value='kwarg', + ), + annotate=dict( + cls=widgets.TextWidget, + value='annotate', + ), + ) + +@nt.with_setup(clear_display) +def test_decorator_kwarg(): + with tt.monkeypatch(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.IntSliderWidget, + value=5, + ) + +@nt.with_setup(clear_display) +def test_decorator_no_call(): + with tt.monkeypatch(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.TextWidget, + value='default', + ) + +@nt.with_setup(clear_display) +def test_call_interact(): + def foo(a='default'): + pass + with tt.monkeypatch(interaction, 'display', record_display): + ifoo = interact(foo) + nt.assert_equal(len(displayed), 1) + w = displayed[0].children[0] + check_widget(w, + cls=widgets.TextWidget, + value='default', + ) + +@nt.with_setup(clear_display) +def test_call_interact_kwargs(): + def foo(a='default'): + pass + with tt.monkeypatch(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.IntSliderWidget, + value=10, + ) + +@nt.with_setup(clear_display) +def test_call_decorated_on_trait_change(): + """test calling @interact decorated functions""" + d = {} + with tt.monkeypatch(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.TextWidget, + 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 tt.monkeypatch(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.TextWidget, + 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.TextWidget, + value='text', + description='b', + ) +