732 lines
19 KiB
Python
732 lines
19 KiB
Python
# Copyright (c) Jupyter Development Team.
|
|
# Distributed under the terms of the Modified BSD License.
|
|
|
|
"""Test interact and interactive."""
|
|
|
|
from __future__ import print_function
|
|
|
|
try:
|
|
from unittest.mock import patch
|
|
except ImportError:
|
|
from mock import patch
|
|
|
|
import os
|
|
from collections import OrderedDict
|
|
import pytest
|
|
|
|
import ipywidgets as widgets
|
|
|
|
from traitlets import TraitError
|
|
from ipywidgets import (interact, interact_manual, interactive,
|
|
interaction, Output)
|
|
from ipython_genutils.py3compat import annotate
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Utility stuff
|
|
#-----------------------------------------------------------------------------
|
|
|
|
from .utils import setup, teardown
|
|
|
|
def f(**kwargs):
|
|
pass
|
|
|
|
displayed = []
|
|
@pytest.fixture()
|
|
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':
|
|
assert w.__class__ is expected
|
|
else:
|
|
value = getattr(w, attr)
|
|
assert value == expected, "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected)
|
|
|
|
# For numeric values, the types should match too
|
|
if isinstance(value, (int, float)):
|
|
tv = type(value)
|
|
te = type(expected)
|
|
assert tv is te, "type(%s.%s) = %r != %r" % (w.__class__.__name__, attr, tv, te)
|
|
|
|
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:
|
|
if not isinstance(w, Output):
|
|
widgets[w.description] = w
|
|
|
|
for key, d in to_check.items():
|
|
assert key in 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_float():
|
|
for a in (2.25, 1.0, -3.5, 0.0):
|
|
if not a:
|
|
expected_min = 0.0
|
|
expected_max = 1.0
|
|
elif a > 0:
|
|
expected_min = -a
|
|
expected_max = 3*a
|
|
else:
|
|
expected_min = 3*a
|
|
expected_max = -a
|
|
c = interactive(f, a=a)
|
|
w = c.children[0]
|
|
check_widget(w,
|
|
cls=widgets.FloatSlider,
|
|
description='a',
|
|
value=a,
|
|
min=expected_min,
|
|
max=expected_max,
|
|
step=0.1,
|
|
readout=True,
|
|
)
|
|
|
|
def test_single_value_int():
|
|
for a in (1, 5, -3, 0):
|
|
if not a:
|
|
expected_min = 0
|
|
expected_max = 1
|
|
elif a > 0:
|
|
expected_min = -a
|
|
expected_max = 3*a
|
|
else:
|
|
expected_min = 3*a
|
|
expected_max = -a
|
|
c = interactive(f, a=a)
|
|
assert len(c.children) == 2
|
|
w = c.children[0]
|
|
check_widget(w,
|
|
cls=widgets.IntSlider,
|
|
description='a',
|
|
value=a,
|
|
min=expected_min,
|
|
max=expected_max,
|
|
step=1,
|
|
readout=True,
|
|
)
|
|
|
|
def test_list_str():
|
|
values = ['hello', 'there', 'guy']
|
|
first = values[0]
|
|
c = interactive(f, lis=values)
|
|
assert len(c.children) == 2
|
|
d = dict(
|
|
cls=widgets.Dropdown,
|
|
value=first,
|
|
options=tuple(values),
|
|
_options_labels=tuple(values),
|
|
_options_values=tuple(values),
|
|
)
|
|
check_widgets(c, lis=d)
|
|
|
|
def test_list_int():
|
|
values = [3, 1, 2]
|
|
first = values[0]
|
|
c = interactive(f, lis=values)
|
|
assert len(c.children) == 2
|
|
d = dict(
|
|
cls=widgets.Dropdown,
|
|
value=first,
|
|
options=tuple(values),
|
|
_options_labels=tuple(str(v) for v in values),
|
|
_options_values=tuple(values),
|
|
)
|
|
check_widgets(c, lis=d)
|
|
|
|
def test_list_tuple():
|
|
values = [(3, 300), (1, 100), (2, 200)]
|
|
first = values[0][1]
|
|
c = interactive(f, lis=values)
|
|
assert len(c.children) == 2
|
|
d = dict(
|
|
cls=widgets.Dropdown,
|
|
value=first,
|
|
options=tuple(values),
|
|
_options_labels=("3", "1", "2"),
|
|
_options_values=(300, 100, 200),
|
|
)
|
|
check_widgets(c, lis=d)
|
|
|
|
def test_list_tuple_invalid():
|
|
for bad in [
|
|
(),
|
|
]:
|
|
with pytest.raises(ValueError):
|
|
print(bad) # because there is no custom message in assert_raises
|
|
c = interactive(f, tup=bad)
|
|
|
|
def test_dict():
|
|
for d in [
|
|
dict(a=5),
|
|
dict(a=5, b='b', c=dict),
|
|
]:
|
|
c = interactive(f, d=d)
|
|
w = c.children[0]
|
|
check = dict(
|
|
cls=widgets.Dropdown,
|
|
description='d',
|
|
value=next(iter(d.values())),
|
|
options=d,
|
|
_options_labels=tuple(d.keys()),
|
|
_options_values=tuple(d.values()),
|
|
)
|
|
check_widget(w, **check)
|
|
|
|
|
|
def test_ordereddict():
|
|
from collections import OrderedDict
|
|
items = [(3, 300), (1, 100), (2, 200)]
|
|
first = items[0][1]
|
|
values = OrderedDict(items)
|
|
c = interactive(f, lis=values)
|
|
assert len(c.children) == 2
|
|
d = dict(
|
|
cls=widgets.Dropdown,
|
|
value=first,
|
|
options=values,
|
|
_options_labels=("3", "1", "2"),
|
|
_options_values=(300, 100, 200),
|
|
)
|
|
check_widgets(c, lis=d)
|
|
|
|
def test_iterable():
|
|
def yield_values():
|
|
yield 3
|
|
yield 1
|
|
yield 2
|
|
first = next(yield_values())
|
|
c = interactive(f, lis=yield_values())
|
|
assert len(c.children) == 2
|
|
d = dict(
|
|
cls=widgets.Dropdown,
|
|
value=first,
|
|
options=(3, 1, 2),
|
|
_options_labels=("3", "1", "2"),
|
|
_options_values=(3, 1, 2),
|
|
)
|
|
check_widgets(c, lis=d)
|
|
|
|
def test_iterable_tuple():
|
|
values = [(3, 300), (1, 100), (2, 200)]
|
|
first = values[0][1]
|
|
c = interactive(f, lis=iter(values))
|
|
assert len(c.children) == 2
|
|
d = dict(
|
|
cls=widgets.Dropdown,
|
|
value=first,
|
|
options=tuple(values),
|
|
_options_labels=("3", "1", "2"),
|
|
_options_values=(300, 100, 200),
|
|
)
|
|
check_widgets(c, lis=d)
|
|
|
|
def test_mapping():
|
|
from collections import Mapping, OrderedDict
|
|
class TestMapping(Mapping):
|
|
def __init__(self, values):
|
|
self.values = values
|
|
def __getitem__(self):
|
|
raise NotImplementedError
|
|
def __len__(self):
|
|
raise NotImplementedError
|
|
def __iter__(self):
|
|
raise NotImplementedError
|
|
def items(self):
|
|
return self.values
|
|
|
|
items = [(3, 300), (1, 100), (2, 200)]
|
|
first = items[0][1]
|
|
values = TestMapping(items)
|
|
c = interactive(f, lis=values)
|
|
assert len(c.children) == 2
|
|
d = dict(
|
|
cls=widgets.Dropdown,
|
|
value=first,
|
|
options=tuple(items),
|
|
_options_labels=("3", "1", "2"),
|
|
_options_values=(300, 100, 200),
|
|
)
|
|
check_widgets(c, lis=d)
|
|
|
|
|
|
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=OrderedDict([('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=OrderedDict([('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',
|
|
),
|
|
)
|
|
|
|
def test_decorator_kwarg(clear_display):
|
|
with patch.object(interaction, 'display', record_display):
|
|
@interact(a=5)
|
|
def foo(a):
|
|
pass
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.IntSlider,
|
|
value=5,
|
|
)
|
|
|
|
def test_interact_instancemethod(clear_display):
|
|
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))
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.IntSlider,
|
|
value=5,
|
|
)
|
|
|
|
def test_decorator_no_call(clear_display):
|
|
with patch.object(interaction, 'display', record_display):
|
|
@interact
|
|
def foo(a='default'):
|
|
pass
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.Text,
|
|
value='default',
|
|
)
|
|
|
|
def test_call_interact(clear_display):
|
|
def foo(a='default'):
|
|
pass
|
|
with patch.object(interaction, 'display', record_display):
|
|
ifoo = interact(foo)
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.Text,
|
|
value='default',
|
|
)
|
|
|
|
def test_call_interact_on_trait_changed_none_return(clear_display):
|
|
def foo(a='default'):
|
|
pass
|
|
with patch.object(interaction, 'display', record_display):
|
|
ifoo = interact(foo)
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.Text,
|
|
value='default',
|
|
)
|
|
with patch.object(interaction, 'display', record_display):
|
|
w.value = 'called'
|
|
assert len(displayed) == 1
|
|
|
|
def test_call_interact_kwargs(clear_display):
|
|
def foo(a='default'):
|
|
pass
|
|
with patch.object(interaction, 'display', record_display):
|
|
ifoo = interact(foo, a=10)
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.IntSlider,
|
|
value=10,
|
|
)
|
|
|
|
def test_call_decorated_on_trait_change(clear_display):
|
|
"""test calling @interact decorated functions"""
|
|
d = {}
|
|
with patch.object(interaction, 'display', record_display):
|
|
@interact
|
|
def foo(a='default'):
|
|
d['a'] = a
|
|
return a
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.Text,
|
|
value='default',
|
|
)
|
|
# test calling the function directly
|
|
a = foo('hello')
|
|
assert a == 'hello'
|
|
assert d['a'] == 'hello'
|
|
|
|
# test that setting trait values calls the function
|
|
with patch.object(interaction, 'display', record_display):
|
|
w.value = 'called'
|
|
assert d['a'] == 'called'
|
|
assert len(displayed) == 2
|
|
assert w.value == displayed[-1]
|
|
|
|
def test_call_decorated_kwargs_on_trait_change(clear_display):
|
|
"""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
|
|
assert len(displayed) == 1
|
|
w = displayed[0].children[0]
|
|
check_widget(w,
|
|
cls=widgets.Text,
|
|
value='kwarg',
|
|
)
|
|
# test calling the function directly
|
|
a = foo('hello')
|
|
assert a == 'hello'
|
|
assert d['a'] == 'hello'
|
|
|
|
# test that setting trait values calls the function
|
|
with patch.object(interaction, 'display', record_display):
|
|
w.value = 'called'
|
|
assert d['a'] == 'called'
|
|
assert len(displayed) == 2
|
|
assert w.value == displayed[-1]
|
|
|
|
|
|
|
|
def test_fixed():
|
|
c = interactive(f, a=widgets.fixed(5), b='text')
|
|
assert len(c.children) == 2
|
|
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'
|
|
assert d == {'b': 'different text'}
|
|
|
|
def test_interact_manual_button():
|
|
c = interact.options(manual=True).widget(f)
|
|
w = c.children[0]
|
|
check_widget(w, cls=widgets.Button)
|
|
|
|
def test_interact_manual_nocall():
|
|
callcount = 0
|
|
def calltest(testarg):
|
|
callcount += 1
|
|
c = interact.options(manual=True)(calltest, testarg=5).widget
|
|
c.children[0].value = 10
|
|
assert callcount == 0
|
|
|
|
def test_interact_call():
|
|
w = interact.widget(f)
|
|
w.update()
|
|
|
|
w = interact_manual.widget(f)
|
|
w.update()
|
|
|
|
def test_interact_options():
|
|
def f(x):
|
|
return x
|
|
w = interact.options(manual=False).options(manual=True)(f, x=21).widget
|
|
assert w.manual == True
|
|
|
|
w = interact_manual.options(manual=False).options()(x=21).widget(f)
|
|
assert w.manual == False
|
|
|
|
w = interact(x=21)().options(manual=True)(f).widget
|
|
assert w.manual == True
|
|
|
|
def test_interact_options_bad():
|
|
with pytest.raises(ValueError):
|
|
interact.options(bad="foo")
|
|
|
|
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.upper = 3
|
|
w.max = 3
|
|
check_widget(w, cls=irsw, value=(2, 3), min=0, 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.lower = 2
|
|
check_widget(w, cls=irsw, value=(2, 6), min=0, max=6)
|
|
|
|
with pytest.raises(TraitError):
|
|
w.min = 7
|
|
with pytest.raises(TraitError):
|
|
w.max = -1
|
|
|
|
w = irsw(min=2, max=3, value=(2, 3))
|
|
check_widget(w, min=2, max=3, value=(2, 3))
|
|
w = irsw(min=100, max=200, value=(125, 175))
|
|
check_widget(w, value=(125, 175))
|
|
|
|
with pytest.raises(TraitError):
|
|
irsw(min=2, max=1)
|
|
|
|
|
|
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.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.lower = .2
|
|
check_widget(w, cls=frsw, value=(.2, .6), min=0., max=.6)
|
|
|
|
with pytest.raises(TraitError):
|
|
w.min = .7
|
|
with pytest.raises(TraitError):
|
|
w.max = -.1
|
|
|
|
w = frsw(min=2, max=3, value=(2.2, 2.5))
|
|
check_widget(w, min=2., max=3.)
|
|
|
|
with pytest.raises(TraitError):
|
|
frsw(min=.2, max=.1)
|
|
|
|
|
|
def test_multiple_selection():
|
|
smw = widgets.SelectMultiple
|
|
|
|
# degenerate multiple select
|
|
w = smw()
|
|
check_widget(w, value=tuple())
|
|
|
|
# don't accept random other value when no options
|
|
with pytest.raises(TraitError):
|
|
w.value = (2,)
|
|
check_widget(w, value=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 pytest.raises(TraitError):
|
|
w.value = w.value + (2,)
|
|
check_widget(w, value=(1,))
|
|
|
|
# change options, which resets value
|
|
w.options = w.options + ((2, 2),)
|
|
check_widget(w, options=((1, 1), (2,2)), value=())
|
|
|
|
# change value
|
|
w.value = (1,2)
|
|
check_widget(w, value=(1, 2))
|
|
|
|
# dict style
|
|
w.options = {1: 1}
|
|
check_widget(w, options={1:1})
|
|
|
|
# updating
|
|
with pytest.raises(TraitError):
|
|
w.value = (2,)
|
|
check_widget(w, options={1:1})
|
|
|
|
|
|
def test_interact_noinspect():
|
|
a = u'hello'
|
|
c = interactive(print, a=a)
|
|
w = c.children[0]
|
|
check_widget(w,
|
|
cls=widgets.Text,
|
|
description='a',
|
|
value=a,
|
|
)
|
|
|
|
|
|
def test_get_interact_value():
|
|
from ipywidgets.widgets import ValueWidget
|
|
from traitlets import Unicode
|
|
class TheAnswer(ValueWidget):
|
|
_model_name = Unicode('TheAnswer')
|
|
description = Unicode()
|
|
def get_interact_value(self):
|
|
return 42
|
|
w = TheAnswer()
|
|
c = interactive(lambda v: v, v=w)
|
|
c.update()
|
|
assert c.result == 42
|
|
|
|
def test_state_schema():
|
|
from ipywidgets.widgets import IntSlider, Widget
|
|
import json
|
|
import jsonschema
|
|
s = IntSlider()
|
|
state = Widget.get_manager_state(drop_defaults=True)
|
|
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../', 'state.schema.json')) as f:
|
|
schema = json.load(f)
|
|
jsonschema.validate(state, schema)
|
|
|