This commit is contained in:
Christian Mantha
2026-03-02 19:10:52 -05:00
commit 2ca0b9ef7c
28907 changed files with 5233713 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,90 @@
# coding: utf-8
# Copyright (c) Vidar Tonaas Fauske.
# Distributed under the terms of the Modified BSD License.
import pytest
import datetime
import pytz
from traitlets import TraitError
from ..trait_types import (
time_to_json,
time_from_json,
datetime_to_json,
datetime_from_json,
)
def test_time_serialize_none():
assert time_to_json(None, None) == None
def test_time_serialize_value():
t = datetime.time(13, 37, 42, 7000)
assert time_to_json(t, None) == dict(
hours=13, minutes=37, seconds=42, milliseconds=7
)
def test_time_deserialize_none():
assert time_from_json(None, None) == None
def test_time_deserialize_value():
v = dict(hours=13, minutes=37, seconds=42, milliseconds=7)
assert time_from_json(v, None) == datetime.time(13, 37, 42, 7000)
def test_datetime_serialize_none():
assert datetime_to_json(None, None) == None
def test_datetime_serialize_value():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7000, pytz.utc)
assert datetime_to_json(t, None) == dict(
year=2002,
month=1, # Months are 0-based indices in JS
date=20,
hours=13,
minutes=37,
seconds=42,
milliseconds=7,
)
def test_datetime_serialize_non_utz():
# Non-existent timezone, so it will never be the local one:
tz = pytz.FixedOffset(42)
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7000, tz)
assert datetime_to_json(t, None) == dict(
year=2002,
month=1, # Months are 0-based indices in JS
date=20,
hours=12,
minutes=55,
seconds=42,
milliseconds=7,
)
def test_datetime_deserialize_none():
assert datetime_from_json(None, None) == None
def test_datetime_deserialize_value():
tz = pytz.FixedOffset(42)
v = dict(
year=2002,
month=1, # Months are 0-based indices in JS
date=20,
hours=13,
minutes=37,
seconds=42,
milliseconds=7,
)
assert datetime_from_json(v, None) == datetime.datetime(
2002, 2, 20, 14, 19, 42, 7000, tz
)

View File

@@ -0,0 +1,27 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from unittest import TestCase
from ipywidgets.widgets.docutils import doc_subst
class TestDocSubst(TestCase):
def test_substitution(self):
snippets = {'key': '62'}
@doc_subst(snippets)
def f():
""" Docstring with value {key} """
assert "Docstring with value 62" in f.__doc__
def test_unused_keys(self):
snippets = {'key': '62', 'other-key': 'unused'}
@doc_subst(snippets)
def f():
""" Docstring with value {key} """
assert "Docstring with value 62" in f.__doc__

View File

@@ -0,0 +1,660 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""Test interact and interactive."""
from unittest.mock import patch
import os
from enum import Enum
from collections import OrderedDict
import pytest
import ipywidgets as widgets
from traitlets import TraitError, Float
from ipywidgets import (interact, interact_manual, interactive,
interaction, Output, Widget)
#-----------------------------------------------------------------------------
# Utility stuff
#-----------------------------------------------------------------------------
def f(**kwargs):
pass
class Color(Enum):
red = 0
green = 1
blue = 2
def g(a: str, b: bool, c: int, d: float, e: Color) -> None:
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, "{}.{} = {!r} != {!r}".format(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({}.{}) = {!r} != {!r}".format(w.__class__.__name__, attr, tv, te)
def check_widget_children(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 = '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_widget_children(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_widget_children(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_widget_children(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_widget_children(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_widget_children(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_widget_children(c, lis=d)
def test_mapping():
from collections.abc import Mapping
from collections import 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_widget_children(c, lis=d)
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:
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) == 2 # display the result and the interact
w = displayed[1].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) == 3
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) == 2 # display the result and the interact
w = displayed[1].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) == 3
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_raises_on_non_value_widget():
""" Test that passing in a non-value widget raises an error """
class BadWidget(Widget):
""" A widget that contains a `value` traitlet """
value = Float()
with pytest.raises(TypeError, match=".* not a ValueWidget.*"):
interactive(f, b=BadWidget())
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
w.options = (1,)
with pytest.raises(TraitError):
w.value = (2,)
check_widget(w, options=(1,) )
def test_interact_noinspect():
a = 'hello'
c = interactive(dict, 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)
def test_type_hints():
c = interactive(g)
assert len(c.children) == 6
check_widget_children(
c,
a={'cls': widgets.Text},
b={'cls': widgets.Checkbox},
c={'cls': widgets.IntText},
d={'cls': widgets.FloatText},
e={
'cls': widgets.Dropdown,
'options': {
'red': Color.red,
'green': Color.green,
'blue': Color.blue,
},
'_options_labels': ("red", "green", "blue"),
'_options_values': (Color.red, Color.green, Color.blue),
},
)

View File

@@ -0,0 +1,38 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import pytest
from .. import jslink, jsdlink, ToggleButton
def test_jslink_args():
with pytest.raises(TypeError):
jslink()
w1 = ToggleButton()
with pytest.raises(TypeError):
jslink((w1, 'value'))
w2 = ToggleButton()
jslink((w1, 'value'), (w2, 'value'))
with pytest.raises(TypeError):
jslink((w1, 'value'), (w2, 'nosuchtrait'))
with pytest.raises(TypeError):
jslink((w1, 'value'), (w2, 'traits'))
def test_jsdlink_args():
with pytest.raises(TypeError):
jsdlink()
w1 = ToggleButton()
with pytest.raises(TypeError):
jsdlink((w1, 'value'))
w2 = ToggleButton()
jsdlink((w1, 'value'), (w2, 'value'))
with pytest.raises(TypeError):
jsdlink((w1, 'value'), (w2, 'nosuchtrait'))
with pytest.raises(TypeError):
jsdlink((w1, 'value'), (w2, 'traits'))

View File

@@ -0,0 +1,168 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from unittest import TestCase
from traitlets import TraitError
from ipywidgets.widgets import Accordion, Tab, Stack, HTML
class TestTab(TestCase):
def setUp(self):
self.children = [HTML('0'), HTML('1')]
self.widget = Tab
def test_selected_index_none(self):
widget = self.widget(self.children, selected_index=None)
state = widget.get_state()
assert state['selected_index'] == 0
def test_selected_index_default(self):
widget = self.widget(self.children)
state = widget.get_state()
assert state['selected_index'] == 0
def test_selected_index(self):
widget = self.widget(self.children, selected_index=1)
state = widget.get_state()
assert state['selected_index'] == 1
def test_selected_index_out_of_bounds(self):
with self.assertRaises(TraitError):
self.widget(self.children, selected_index=-1)
def test_children_position_argument(self):
self.widget(self.children)
def test_titles(self):
widget = self.widget(self.children, selected_index=None)
assert widget.get_state()['titles'] == ('', '')
assert widget.titles == ('', '')
widget.set_title(1, 'Title 1')
assert widget.get_state()['titles'] == ('', 'Title 1')
assert widget.titles[1] == 'Title 1'
assert widget.get_title(1) == 'Title 1'
# Backwards compatible with 7.x api
widget.set_title(1, None)
assert widget.get_state()['titles'] == ('', '')
assert widget.titles[1] == ''
assert widget.get_title(1) == ''
with self.assertRaises(IndexError):
widget.set_title(2, 'out of bounds')
with self.assertRaises(IndexError):
widget.get_title(2)
widget.children = tuple(widget.children[:1])
assert len(widget.children) == 1
assert widget.titles == ('',)
class TestAccordion(TestCase):
def setUp(self):
self.children = [HTML('0'), HTML('1')]
self.widget = Accordion
def test_selected_index_none(self):
widget = self.widget(self.children, selected_index=None)
state = widget.get_state()
assert state['selected_index'] is None
def test_selected_index_default(self):
widget = self.widget(self.children)
state = widget.get_state()
assert state['selected_index'] is None
def test_selected_index(self):
widget = self.widget(self.children, selected_index=1)
state = widget.get_state()
assert state['selected_index'] == 1
def test_selected_index_out_of_bounds(self):
with self.assertRaises(TraitError):
self.widget(self.children, selected_index=-1)
def test_children_position_argument(self):
self.widget(self.children)
def test_titles(self):
widget = self.widget(self.children, selected_index=None)
assert widget.get_state()['titles'] == ('', '')
assert widget.titles == ('', '')
widget.set_title(1, 'Title 1')
assert widget.get_state()['titles'] == ('', 'Title 1')
assert widget.titles[1] == 'Title 1'
assert widget.get_title(1) == 'Title 1'
# Backwards compatible with 7.x api
widget.set_title(1, None)
assert widget.get_state()['titles'] == ('', '')
assert widget.titles[1] == ''
assert widget.get_title(1) == ''
with self.assertRaises(IndexError):
widget.set_title(2, 'out of bounds')
with self.assertRaises(IndexError):
widget.get_title(2)
widget.children = tuple(widget.children[:1])
assert len(widget.children) == 1
assert widget.titles == ('',)
class TestStack(TestCase):
def setUp(self):
self.children = [HTML('0'), HTML('1')]
self.widget = Stack
def test_selected_index_none(self):
widget = self.widget(self.children, selected_index=None)
state = widget.get_state()
assert state['selected_index'] is None
def test_selected_index_default(self):
widget = self.widget(self.children)
state = widget.get_state()
assert state['selected_index'] is None
def test_selected_index(self):
widget = self.widget(self.children, selected_index=1)
state = widget.get_state()
assert state['selected_index'] == 1
def test_selected_index_out_of_bounds(self):
with self.assertRaises(TraitError):
self.widget(self.children, selected_index=-1)
def test_children_position_argument(self):
self.widget(self.children)
def test_titles(self):
widget = self.widget(self.children, selected_index=None)
assert widget.get_state()['titles'] == ('', '')
assert widget.titles == ('', '')
widget.set_title(1, 'Title 1')
assert widget.get_state()['titles'] == ('', 'Title 1')
assert widget.titles[1] == 'Title 1'
assert widget.get_title(1) == 'Title 1'
# Backwards compatible with 7.x api
widget.set_title(1, None)
assert widget.get_state()['titles'] == ('', '')
assert widget.titles[1] == ''
assert widget.get_title(1) == ''
with self.assertRaises(IndexError):
widget.set_title(2, 'out of bounds')
with self.assertRaises(IndexError):
widget.get_title(2)
widget.children = tuple(widget.children[:1])
assert len(widget.children) == 1
assert widget.titles == ('',)

View File

@@ -0,0 +1,31 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from traitlets import Bool, Tuple, List
from .utils import setup
from ..widget import Widget
from ..._version import __control_protocol_version__
# A widget with simple traits
class SimpleWidget(Widget):
a = Bool().tag(sync=True)
b = Tuple(Bool(), Bool(), Bool(), default_value=(False, False, False)).tag(
sync=True
)
c = List(Bool()).tag(sync=True)
def test_empty_send_state():
w = SimpleWidget()
w.send_state([])
assert w.comm.messages == []
def test_empty_hold_sync():
w = SimpleWidget()
with w.hold_sync():
pass
assert w.comm.messages == []

View File

@@ -0,0 +1,389 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import pytest
from unittest import mock
from traitlets import Bool, Tuple, List, Instance, CFloat, CInt, Float, Int, TraitError, observe
from .utils import setup
import ipywidgets
from ipywidgets import Widget
@pytest.fixture(params=[True, False])
def echo(request):
oldvalue = ipywidgets.widgets.widget.JUPYTER_WIDGETS_ECHO
ipywidgets.widgets.widget.JUPYTER_WIDGETS_ECHO = request.param
yield request.param
ipywidgets.widgets.widget.JUPYTER_WIDGETS_ECHO = oldvalue
#
# First some widgets to test on:
#
# A widget with simple traits (list + tuple to ensure both are handled)
class SimpleWidget(Widget):
a = Bool().tag(sync=True)
b = Tuple(Bool(), Bool(), Bool(), default_value=(False, False, False)).tag(sync=True)
c = List(Bool()).tag(sync=True)
# A widget with various kinds of number traits
class NumberWidget(Widget):
f = Float().tag(sync=True)
cf = CFloat().tag(sync=True)
i = Int().tag(sync=True)
ci = CInt().tag(sync=True)
# A widget where the data might be changed on reception:
def transform_fromjson(data, widget):
# Switch the two last elements when setting from json, if the first element is True
# and always set first element to False
if not data[0]:
return data
return [False] + data[1:-2] + [data[-1], data[-2]]
class TransformerWidget(Widget):
d = List(Bool()).tag(sync=True, from_json=transform_fromjson)
# A widget that has a buffer:
class DataInstance():
def __init__(self, data=None):
self.data = data
def mview_serializer(instance, widget):
return { 'data': memoryview(instance.data) if instance.data else None }
def bytes_serializer(instance, widget):
return { 'data': bytearray(memoryview(instance.data).tobytes()) if instance.data else None }
def deserializer(json_data, widget):
return DataInstance( memoryview(json_data['data']).tobytes() if json_data else None )
class DataWidget(SimpleWidget):
d = Instance(DataInstance, args=()).tag(sync=True, to_json=mview_serializer, from_json=deserializer)
# A widget that has a buffer that might be changed on reception:
def truncate_deserializer(json_data, widget):
return DataInstance( json_data['data'][:20].tobytes() if json_data else None )
class TruncateDataWidget(SimpleWidget):
d = Instance(DataInstance, args=()).tag(sync=True, to_json=bytes_serializer, from_json=truncate_deserializer)
#
# Actual tests:
#
def test_set_state_simple(echo):
w = SimpleWidget()
w.set_state(dict(
a=True,
b=[True, False, True],
c=[False, True, False],
))
assert len(w.comm.messages) == (1 if echo else 0)
def test_set_state_transformer(echo):
w = TransformerWidget()
w.set_state(dict(
d=[True, False, True]
))
# Since the deserialize step changes the state, this should send an update
expected = []
if echo:
expected.append(
((), dict(
buffers=[],
data=dict(
buffer_paths=[],
method='echo_update',
state=dict(d=[True, False, True]),
))))
expected.append(
((), dict(
buffers=[],
data=dict(
buffer_paths=[],
method='update',
state=dict(d=[False, True, False]),
))))
assert w.comm.messages == expected
def test_set_state_data(echo):
w = DataWidget()
data = memoryview(b'x'*30)
w.set_state(dict(
a=True,
d={'data': data},
))
assert len(w.comm.messages) == (1 if echo else 0)
def test_set_state_data_truncate(echo):
w = TruncateDataWidget()
data = memoryview(b'x'*30)
w.set_state(dict(
a=True,
d={'data': data},
))
# Get message for checking
assert len(w.comm.messages) == 2 if echo else 1 # ensure we didn't get more than expected
msg = w.comm.messages[-1]
# Assert that the data update (truncation) sends an update
buffers = msg[1].pop('buffers')
assert msg == ((), dict(
data=dict(
method='update',
state=dict(d={}),
buffer_paths=[['d', 'data']]
)))
# Sanity:
assert len(buffers) == 1
assert buffers[0] == data[:20].tobytes()
def test_set_state_numbers_int(echo):
# JS does not differentiate between float/int.
# Instead, it formats exact floats as ints in JSON (1.0 -> '1').
w = NumberWidget()
# Set everything with ints
w.set_state(dict(
f = 1,
cf = 2,
i = 3,
ci = 4,
))
# Ensure one update message gets produced
assert len(w.comm.messages) == (1 if echo else 0)
def test_set_state_numbers_float(echo):
w = NumberWidget()
# Set floats to int-like floats
w.set_state(dict(
f = 1.0,
cf = 2.0,
ci = 4.0
))
# Ensure one update message gets produced
assert len(w.comm.messages) == (1 if echo else 0)
def test_set_state_float_to_float(echo):
w = NumberWidget()
# Set floats to float
w.set_state(dict(
f = 1.2,
cf = 2.6,
))
# Ensure one message gets produced
assert len(w.comm.messages) == (1 if echo else 0)
def test_set_state_cint_to_float(echo):
w = NumberWidget()
# Set CInt to float
w.set_state(dict(
ci = 5.6
))
# Ensure an update message gets produced
assert len(w.comm.messages) == (2 if echo else 1)
msg = w.comm.messages[-1]
data = msg[1]['data']
assert data['method'] == 'update'
assert data['state'] == {'ci': 5}
# This test is disabled, meaning ipywidgets REQUIRES
# any JSON received to format int-like numbers as ints
def _x_test_set_state_int_to_int_like():
# Note: Setting i to an int-like float will produce an
# error, so if JSON producer were to always create
# float formatted numbers, this would fail!
w = NumberWidget()
# Set floats to int-like floats
w.set_state(dict(
i = 3.0
))
# Ensure no update message gets produced
assert len(w.comm.messages) == 0
def test_set_state_int_to_float(echo):
w = NumberWidget()
# Set Int to float
with pytest.raises(TraitError):
w.set_state(dict(
i = 3.5
))
def test_property_lock(echo):
# when this widget's value is set to 42, it sets itself to 2, and then back to 42 again (and then stops)
class AnnoyingWidget(Widget):
value = Float().tag(sync=True)
stop = Bool(False)
@observe('value')
def _propagate_value(self, change):
print('_propagate_value', change.new)
if self.stop:
return
if change.new == 42:
self.value = 2
if change.new == 2:
self.stop = True
self.value = 42
widget = AnnoyingWidget(value=1)
assert widget.value == 1
widget._send = mock.MagicMock()
# this mimics a value coming from the front end
widget.set_state({'value': 42})
assert widget.value == 42
assert widget.stop is True
# we expect no new state to be sent
calls = []
widget._send.assert_has_calls(calls)
def test_hold_sync(echo):
# when this widget's value is set to 42, it sets the value to 2, and also sets a different trait value
class AnnoyingWidget(Widget):
value = Float().tag(sync=True)
other = Float().tag(sync=True)
@observe('value')
def _propagate_value(self, change):
print('_propagate_value', change.new)
if change.new == 42:
self.value = 2
self.other = 11
widget = AnnoyingWidget(value=1)
assert widget.value == 1
widget._send = mock.MagicMock()
# this mimics a value coming from the front end
widget.set_state({'value': 42})
assert widget.value == 2
assert widget.other == 11
msg = {'method': 'echo_update', 'state': {'value': 42.0}, 'buffer_paths': []}
call42 = mock.call(msg, buffers=[])
msg = {'method': 'update', 'state': {'value': 2.0}, 'buffer_paths': []}
call2 = mock.call(msg, buffers=[])
msg = {'method': 'update', 'state': {'other': 11.0}, 'buffer_paths': []}
call11 = mock.call(msg, buffers=[])
calls = [call42, call2, call11] if echo else [call2, call11]
widget._send.assert_has_calls(calls)
def test_echo():
# we always echo values back to the frontend
class ValueWidget(Widget):
value = Float().tag(sync=True)
widget = ValueWidget(value=1)
assert widget.value == 1
widget._send = mock.MagicMock()
# this mimics a state coming from the front end
widget.set_state({'value': 42, 'unexpected_field': 43})
assert widget.value == 42
# we expect this to be echoed
msg = {'method': 'echo_update', 'state': {'value': 42.0}, 'buffer_paths': []}
call42 = mock.call(msg, buffers=[])
calls = [call42]
widget._send.assert_has_calls(calls)
def test_echo_single():
# we always echo multiple changes back in 1 update
class ValueWidget(Widget):
value = Float().tag(sync=True)
square = Float().tag(sync=True)
@observe('value')
def _square(self, change):
self.square = self.value**2
widget = ValueWidget(value=1)
assert widget.value == 1
widget._send = mock.MagicMock()
# this mimics a value coming from the front end
widget._handle_msg({
'content': {
'data': {
'method': 'update',
'state': {
'value': 8,
}
}
}
})
assert widget.value == 8
assert widget.square == 64
# we expect this to be echoed
# note that only value is echoed, not square
msg = {'method': 'echo_update', 'state': {'value': 8.0}, 'buffer_paths': []}
call = mock.call(msg, buffers=[])
msg = {'method': 'update', 'state': {'square': 64}, 'buffer_paths': []}
call2 = mock.call(msg, buffers=[])
calls = [call, call2]
widget._send.assert_has_calls(calls)
def test_no_echo(echo):
# in cases where values coming from the frontend are 'heavy', we might want to opt out
class ValueWidget(Widget):
value = Float().tag(sync=True, echo_update=False)
widget = ValueWidget(value=1)
assert widget.value == 1
widget._send = mock.MagicMock()
# this mimics a value coming from the front end
widget._handle_msg({
'content': {
'data': {
'method': 'update',
'state': {
'value': 42,
}
}
}
})
assert widget.value == 42
# widget._send.assert_not_called(calls)
widget._send.assert_not_called()
# a regular set should sync to the frontend
widget.value = 43
widget._send.assert_has_calls([mock.call({'method': 'update', 'state': {'value': 43.0}, 'buffer_paths': []}, buffers=[])])

View File

@@ -0,0 +1,262 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""Test trait types of the widget packages."""
import array
import datetime as dt
import pytest
from unittest import TestCase
from traitlets import HasTraits, Int, TraitError
from traitlets.tests.test_traitlets import TraitTestBase
from ipywidgets import Color, NumberFormat
from ipywidgets.widgets.widget import _remove_buffers, _put_buffers
from ipywidgets.widgets.trait_types import date_serialization, TypedTuple
class NumberFormatTrait(HasTraits):
value = NumberFormat(".3f")
class TestNumberFormat(TraitTestBase):
obj = NumberFormatTrait()
_good_values = [
'.2f', '.0%', '($.2f', '+20', '.^20', '.2s', '#x', ',.2r',
' .2f', '.2', ''
]
_bad_values = [52, False, 'broken', '..2f', '.2a']
class ColorTrait(HasTraits):
value = Color("black")
class TestColor(TraitTestBase):
obj = ColorTrait()
_good_values = [
"blue", # valid color name
"#AA0", # single digit hex
"#FFFFFF", # double digit hex
"transparent", # special color name
'#aaaa', # single digit hex with alpha
'#ffffffff', # double digit hex with alpha
'rgb(0, 0, 0)', # rgb
'rgb( 20,70,50 )', # rgb with spaces
'rgba(10,10,10, 0.5)', # rgba with float alpha
'rgba(255, 255, 255, 255)', # out of bounds alpha (spec says clamp to 1)
'hsl(0.0, .0, 0)', # hsl
'hsl( 0.5,0.3,0 )', # hsl with spaces
'hsla(10,10,10, 0.5)', # rgba with float alpha
'var(--my-color)', # CSS variable without fallback
'var(--my-color-with_separators)', # CSS variable without fallback
'var(--my-color,)', # CSS variable with empty fallback
'var(--my-color-æ)', # CSS variable with non-ascii characters
'var(--my-color-\u1234)', # CSS variable with unicode characters
r'var(--my-color-\\1234)', # CSS variable escaped hex character
'var(--my-color-\.)', # CSS variable with escaped characters
'var(--my-color,black)', # CSS variable with named color fallback
'var(--my-color, black)', # CSS variable with named color fallback
'var(--my-color, rgb(20, 70, 50))', # CSS variable with rgb color fallback
'var(--my-color, #fff)', # CSS variable with rgb color fallback
]
_bad_values = [
"vanilla", "blues", # Invalid color names
1.2, 0.0, # Should fail with float input
0, 1, 2, # Should fail with int input
'rgb(0.4, 512, -40)',
'hsl(0.4, 512, -40)',
'rgba(0, 0, 0)',
'hsla(0, 0, 0)',
'var(-my-color)', # wrong identifier
'var(--my-color-\u2041)', # invalid unicode codepoint
'var(my-color, black)', # wrong identifier
'var(my-color-., black)', # invalid character in identifier
'var(--my-color, vanilla)', # wrong fallback
'var(--my-color, rgba(0,0,0))', # wrong fallback
None,
]
class ColorTraitWithNone(HasTraits):
value = Color("black", allow_none=True)
class TestColorWithNone(TraitTestBase):
obj = ColorTraitWithNone()
_good_values = TestColor._good_values + [None]
_bad_values = list(filter(lambda v: v is not None, TestColor._bad_values))
class TestDateSerialization(TestCase):
def setUp(self):
self.to_json = date_serialization['to_json']
self.dummy_manager = None
def test_serialize_none(self):
self.assertIs(self.to_json(None, self.dummy_manager), None)
def test_serialize_date(self):
date = dt.date(1900, 2, 18)
expected = {
'year': 1900,
'month': 1,
'date': 18
}
self.assertEqual(self.to_json(date, self.dummy_manager), expected)
class TestDateDeserialization(TestCase):
def setUp(self):
self.from_json = date_serialization['from_json']
self.dummy_manager = None
def test_deserialize_none(self):
self.assertIs(self.from_json(None, self.dummy_manager), None)
def test_deserialize_date(self):
serialized_date = {
'year': 1900,
'month': 1,
'date': 18
}
expected = dt.date(1900, 2, 18)
self.assertEqual(
self.from_json(serialized_date, self.dummy_manager),
expected
)
class TestBuffers(TestCase):
def test_remove_and_put_buffers(self):
mv1 = memoryview(b'test1')
mv2 = memoryview(b'test2')
state = {'plain': [0, 'text'], # should not get removed
'x': {'ar': mv1}, # should result in an empty dict
'y': {'shape': (10, 10), 'data': mv1},
'z': (mv1, mv2), # tests tuple assignment
'top': mv1, # test a top level removal
'deep': {'a': 1, 'b':[0,{'deeper':mv2}]}} # deeply nested
plain = state['plain']
x = state['x']
y = state['y']
y_shape = y['shape']
state_before = state
state, buffer_paths, buffers = _remove_buffers(state)
# check if buffers are removed
self.assertIn('plain', state)
self.assertIn('shape', state['y'])
self.assertNotIn('ar', state['x'])
self.assertEqual(state['x'], {})
self.assertNotIn('data', state['y'])
self.assertNotIn(mv1, state['z'])
self.assertNotIn(mv1, state['z'])
self.assertNotIn('top', state)
self.assertIn('deep', state)
self.assertIn('b', state['deep'])
self.assertNotIn('deeper', state['deep']['b'][1])
# check that items that didn't need change aren't touched
self.assertIsNot(state, state_before)
self.assertIs(state['plain'], plain)
self.assertIsNot(state['x'], x)
self.assertIsNot(state['y'], y)
self.assertIs(state['y']['shape'], y_shape)
# check that the buffer paths really point to the right buffer
for path, buffer in [(['x', 'ar'], mv1), (['y', 'data'], mv1), (['z', 0], mv1), (['z', 1], mv2),\
(['top'], mv1), (['deep', 'b', 1, 'deeper'], mv2)]:
self.assertIn(path, buffer_paths, "%r not in path" % path)
index = buffer_paths.index(path)
self.assertEqual(buffer, buffers[index])
# and check that we can put it back together again
_put_buffers(state, buffer_paths, buffers)
# we know that tuples get converted to list, so help the comparison by changing the tuple to a list
state_before['z'] = list(state_before['z'])
self.assertEqual(state_before, state)
def test_typed_tuple_uninitialized_ints():
class TestCase(HasTraits):
value = TypedTuple(trait=Int())
obj = TestCase()
assert obj.value == ()
def test_typed_tuple_init_ints():
class TestCase(HasTraits):
value = TypedTuple(trait=Int())
obj = TestCase(value=(1, 2, 3))
assert obj.value == (1, 2, 3)
def test_typed_tuple_set_ints():
class TestCase(HasTraits):
value = TypedTuple(trait=Int())
obj = TestCase()
obj.value = (1, 2, 3)
assert obj.value == (1, 2, 3)
def test_typed_tuple_default():
class TestCase(HasTraits):
value = TypedTuple(default_value=(1, 2, 3))
obj = TestCase()
assert obj.value == (1, 2, 3)
def test_typed_tuple_mixed_default():
class TestCase(HasTraits):
value = TypedTuple(default_value=(1, 2, 'foobar'))
obj = TestCase()
assert obj.value == (1, 2, 'foobar')
def test_typed_tuple_bad_default():
class TestCase(HasTraits):
value = TypedTuple(trait=Int(), default_value=(1, 2, 'foobar'))
with pytest.raises(TraitError):
obj = TestCase()
a = obj.value # a read might be needed to trigger default validation
def test_typed_tuple_bad_set():
class TestCase(HasTraits):
value = TypedTuple(trait=Int())
obj = TestCase()
with pytest.raises(TraitError):
obj.value = (1, 2, 'foobar')
def test_typed_tuple_positional_trait():
class TestCase(HasTraits):
value = TypedTuple(Int())
obj = TestCase(value=(1, 2, 3))
assert obj.value == (1, 2, 3)
def test_typed_tuple_positional_default():
class TestCase(HasTraits):
value = TypedTuple((1, 2, 3))
obj = TestCase()
assert obj.value == (1, 2, 3)

View File

@@ -0,0 +1,72 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import inspect
import pytest
from ..utils import deprecation
from .utils import call_method
CALL_PATH = inspect.getfile(call_method)
def test_deprecation():
caller_path = inspect.stack(context=0)[1].filename
with pytest.deprecated_call() as record:
deprecation('Deprecated call')
# Make sure the deprecation pointed to the external function calling this test function
assert len(record) == 1
assert record[0].filename == caller_path
with pytest.deprecated_call() as record:
deprecation('Deprecated call', ['ipywidgets/widgets/tests'])
# Make sure the deprecation pointed to the external function calling this test function
assert len(record) == 1
assert record[0].filename == caller_path
with pytest.deprecated_call() as record:
deprecation('Deprecated call', 'ipywidgets/widgets/tests')
# Make sure the deprecation pointed to the external function calling this test function
assert len(record) == 1
assert record[0].filename == caller_path
with pytest.deprecated_call() as record:
deprecation('Deprecated call', [])
# Make sure the deprecation pointed to *this* file
assert len(record) == 1
assert record[0].filename == __file__
def test_deprecation_indirect():
# If the line that calls "deprecation" is not internal, it is considered the source:
with pytest.warns(DeprecationWarning) as record:
call_method(deprecation, "test message", [])
assert len(record) == 1
assert record[0].filename == CALL_PATH
def test_deprecation_indirect_internal():
# If the line that calls "deprecation" is internal, it is not considered the source:
with pytest.warns(DeprecationWarning) as record:
call_method(deprecation, "test message", [CALL_PATH])
assert len(record) == 1
assert record[0].filename == __file__
def test_deprecation_nested1():
def level1():
deprecation("test message", [])
with pytest.warns(DeprecationWarning) as record:
call_method(level1)
assert len(record) == 1
assert record[0].filename == __file__
def test_deprecation_nested2():
def level2():
deprecation("test message", [])
def level1():
level2()
with pytest.warns(DeprecationWarning) as record:
call_method(level1)
assert len(record) == 1
assert record[0].filename == __file__

View File

@@ -0,0 +1,91 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""Test Widget."""
import inspect
import pytest
from IPython.core.interactiveshell import InteractiveShell
from IPython.display import display
from IPython.utils.capture import capture_output
from .. import widget
from ..widget import Widget
from ..widget_button import Button
import copy
def test_no_widget_view():
# ensure IPython shell is instantiated
# otherwise display() just calls print
shell = InteractiveShell.instance()
with capture_output() as cap:
w = Widget()
display(w)
assert len(cap.outputs) == 1, "expect 1 output"
mime_bundle = cap.outputs[0].data
assert mime_bundle["text/plain"] == repr(w), "expected plain text output"
assert (
"application/vnd.jupyter.widget-view+json" not in mime_bundle
), "widget has no view"
assert cap.stdout == "", repr(cap.stdout)
assert cap.stderr == "", repr(cap.stderr)
def test_widget_view():
# ensure IPython shell is instantiated
# otherwise display() just calls print
shell = InteractiveShell.instance()
with capture_output() as cap:
w = Button()
display(w)
assert len(cap.outputs) == 1, "expect 1 output"
mime_bundle = cap.outputs[0].data
assert mime_bundle["text/plain"] == repr(w), "expected plain text output"
assert (
"application/vnd.jupyter.widget-view+json" in mime_bundle
), "widget should have have a view"
assert cap.stdout == "", repr(cap.stdout)
assert cap.stderr == "", repr(cap.stderr)
def test_close_all():
# create a couple of widgets
widgets = [Button() for i in range(10)]
assert len(widget._instances) > 0, "expect active widgets"
assert widget._instances[widgets[0].model_id] is widgets[0]
# close all the widgets
Widget.close_all()
assert len(widget._instances) == 0, "active widgets should be cleared"
def test_compatibility():
button = Button()
assert widget._instances[button.model_id] is button
with pytest.deprecated_call() as record:
assert widget._instances is widget.Widget.widgets
assert widget._instances is widget.Widget._active_widgets
assert widget._registry is widget.Widget.widget_types
assert widget._registry is widget.Widget._widget_types
Widget.close_all()
assert not widget.Widget.widgets
assert not widget.Widget._active_widgets
caller_path = inspect.stack(context=0)[1].filename
assert all(x.filename == caller_path for x in record)
assert len(record) == 6
def test_widget_copy():
button = Button()
with pytest.raises(NotImplementedError):
copy.copy(button)
with pytest.raises(NotImplementedError):
copy.deepcopy(button)

View File

@@ -0,0 +1,33 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from unittest import TestCase
from traitlets import TraitError
import ipywidgets as widgets
class TestBox(TestCase):
def test_construction(self):
box = widgets.Box()
assert box.get_state()['children'] == []
def test_construction_with_children(self):
html = widgets.HTML('some html')
slider = widgets.IntSlider()
box = widgets.Box([html, slider])
children_state = box.get_state()['children']
assert children_state == [
widgets.widget._widget_to_json(html, None),
widgets.widget._widget_to_json(slider, None),
]
def test_construction_style(self):
box = widgets.Box(box_style='warning')
assert box.get_state()['box_style'] == 'warning'
def test_construction_invalid_style(self):
with self.assertRaises(TraitError):
widgets.Box(box_style='invalid')

View File

@@ -0,0 +1,12 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import inspect
import pytest
from ipywidgets import Button
def test_deprecation_fa_icons():
with pytest.deprecated_call() as record:
Button(icon='fa-home')
assert len(record) == 1
assert record[0].filename == inspect.stack(context=0)[1].filename

View File

@@ -0,0 +1,156 @@
# coding: utf-8
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import pytest
from contextlib import nullcontext
import datetime
import itertools
import pytz
from traitlets import TraitError
from ..widget_datetime import DatetimePicker
dt_1442 = datetime.datetime(1442, 1, 1, tzinfo=pytz.utc)
dt_1664 = datetime.datetime(1664, 1, 1, tzinfo=pytz.utc)
dt_1994 = datetime.datetime(1994, 1, 1, tzinfo=pytz.utc)
dt_2002 = datetime.datetime(2002, 2, 20, 13, 37, 42, 7, tzinfo=pytz.utc)
dt_2056 = datetime.datetime(2056, 1, 1, tzinfo=pytz.utc)
def test_time_creation_blank():
w = DatetimePicker()
assert w.value is None
def test_time_creation_value():
dt = datetime.datetime.now(pytz.utc)
w = DatetimePicker(value=dt)
assert w.value is dt
def test_datetime_validate_value_none():
dt = dt_2002
dt_min = dt_1442
dt_max = dt_2056
w = DatetimePicker(value=dt, min=dt_min, max=dt_max)
w.value = None
assert w.value is None
def _permuted_dts():
ret = []
combos = list(itertools.product([None, dt_1442, dt_2002, dt_2056], repeat=3))
for vals in combos:
expected = vals[0]
if vals[1] and vals[2] and vals[1] > vals[2]:
expected = TraitError
elif vals[0] is None:
pass
elif vals[1] and vals[1] > vals[0]:
expected = vals[1]
elif vals[2] and vals[2] < vals[0]:
expected = vals[2]
ret.append(vals + (expected,))
return ret
@pytest.mark.parametrize(
"input_value,input_min,input_max,expected",
_permuted_dts()
)
def test_datetime_cross_validate_value_min_max(
input_value,
input_min,
input_max,
expected,
):
w = DatetimePicker(value=dt_2002, min=dt_2002, max=dt_2002)
should_raise = expected is TraitError
with pytest.raises(expected) if should_raise else nullcontext():
with w.hold_trait_notifications():
w.value = input_value
w.min = input_min
w.max = input_max
if not should_raise:
assert w.value is expected
def test_datetime_validate_value_vs_min():
dt = dt_2002
dt_min = datetime.datetime(2019, 1, 1, tzinfo=pytz.utc)
dt_max = dt_2056
w = DatetimePicker(min=dt_min, max=dt_max)
w.value = dt
assert w.value.year == 2019
def test_datetime_validate_value_vs_max():
dt = dt_2002
dt_min = dt_1664
dt_max = dt_1994
w = DatetimePicker(min=dt_min, max=dt_max)
w.value = dt
assert w.value.year == 1994
def test_datetime_validate_min_vs_value():
dt = dt_2002
dt_min = datetime.datetime(2019, 1, 1, tzinfo=pytz.utc)
dt_max = dt_2056
w = DatetimePicker(value=dt, max=dt_max)
w.min = dt_min
assert w.value.year == 2019
def test_datetime_validate_min_vs_max():
dt = dt_2002
dt_min = datetime.datetime(2112, 1, 1, tzinfo=pytz.utc)
dt_max = dt_2056
w = DatetimePicker(value=dt, max=dt_max)
with pytest.raises(TraitError):
w.min = dt_min
def test_datetime_validate_max_vs_value():
dt = dt_2002
dt_min = dt_1664
dt_max = dt_1994
w = DatetimePicker(value=dt, min=dt_min)
w.max = dt_max
assert w.value.year == 1994
def test_datetime_validate_max_vs_min():
dt = dt_2002
dt_min = dt_1664
dt_max = datetime.datetime(1337, 1, 1, tzinfo=pytz.utc)
w = DatetimePicker(value=dt, min=dt_min)
with pytest.raises(TraitError):
w.max = dt_max
def test_datetime_validate_naive():
dt = dt_2002
dt_min = dt_1442
dt_max = dt_2056
w = DatetimePicker(value=dt, min=dt_min, max=dt_max)
with pytest.raises(TraitError):
w.max = dt_max.replace(tzinfo=None)
with pytest.raises(TraitError):
w.min = dt_min.replace(tzinfo=None)
with pytest.raises(TraitError):
w.value = dt.replace(tzinfo=None)
def test_datetime_tzinfo():
tz = pytz.timezone('Australia/Sydney')
dt = datetime.datetime(2002, 2, 20, 13, 37, 42, 7, tzinfo=tz)
w = DatetimePicker(value=dt)
assert w.value == dt
# tzinfo only changes upon input from user
assert w.value.tzinfo == tz

View File

@@ -0,0 +1,22 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from unittest import TestCase
from traitlets import TraitError
from ipywidgets import FloatSlider
class TestFloatSlider(TestCase):
def test_construction(self):
FloatSlider()
def test_construction_readout_format(self):
slider = FloatSlider(readout_format='$.1f')
assert slider.get_state()['readout_format'] == '$.1f'
def test_construction_invalid_readout_format(self):
with self.assertRaises(TraitError):
FloatSlider(readout_format='broken')

View File

@@ -0,0 +1,173 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""Test Image widget"""
import io
import os
from ipywidgets import Image
import hashlib
import pkgutil
import tempfile
from contextlib import contextmanager
# Data
@contextmanager
def get_logo_png():
# Once the tests are not in the package, this context manager can be
# replaced with the location of the actual file
LOGO_DATA = pkgutil.get_data('ipywidgets.widgets.tests',
'data/jupyter-logo-transparent.png')
handle, fname = tempfile.mkstemp()
os.close(handle)
with open(fname, 'wb') as f:
f.write(LOGO_DATA)
yield fname
os.remove(fname)
LOGO_PNG_DIGEST = '3ff9eafd7197083153e83339a72e7a335539bae189c33554c680e4382c98af02'
def test_empty_image():
# Empty images shouldn't raise any errors
Image()
def test_image_value():
random_bytes = b'\x0ee\xca\x80\xcd\x9ak#\x7f\x07\x03\xa7'
Image(value=random_bytes)
def test_image_format():
# Test that these format names don't throw an error
Image(format='png')
Image(format='jpeg')
Image(format='url')
def test_from_filename():
with get_logo_png() as LOGO_PNG:
img = Image.from_file(LOGO_PNG)
assert_equal_hash(img.value, LOGO_PNG_DIGEST)
def test_set_from_filename():
img = Image()
with get_logo_png() as LOGO_PNG:
img.set_value_from_file(LOGO_PNG)
assert_equal_hash(img.value, LOGO_PNG_DIGEST)
def test_from_file():
with get_logo_png() as LOGO_PNG:
with open(LOGO_PNG, 'rb') as f:
img = Image.from_file(f)
assert_equal_hash(img.value, LOGO_PNG_DIGEST)
def test_set_value_from_file():
img = Image()
with get_logo_png() as LOGO_PNG:
with open(LOGO_PNG, 'rb') as f:
img.set_value_from_file(f)
assert_equal_hash(img.value, LOGO_PNG_DIGEST)
def test_from_url_unicode():
img = Image.from_url('https://jupyter.org/assets/main-logo.svg')
assert img.value == b'https://jupyter.org/assets/main-logo.svg'
def test_from_url_bytes():
img = Image.from_url(b'https://jupyter.org/assets/main-logo.svg')
assert img.value == b'https://jupyter.org/assets/main-logo.svg'
def test_format_inference_filename():
with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as f:
name = f.name
f.close() # Allow tests to run on Windows
img = Image.from_file(name)
assert img.format == 'svg+xml'
def test_format_inference_file():
with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as f:
img = Image.from_file(f)
assert img.format == 'gif'
def test_format_inference_stream():
# There's no way to infer the format, so it should default to png
fstream = io.BytesIO(b'')
img = Image.from_file(fstream)
assert img.format == 'png'
def test_serialize():
fstream = io.BytesIO(b'123')
img = Image.from_file(fstream)
img_state = img.get_state()
# for python27 it is a memoryview
assert isinstance(img_state['value'], (bytes, memoryview))
# make sure it is (for python 3), since that is what it will be once it comes off the wire
img_state['value'] = memoryview(img_state['value'])
# check that we can deserialize it and get back the original value
img_copy = Image()
img_copy.set_state(img_state)
assert img.value == img_copy.value
def test_format_inference_overridable():
with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as f:
name = f.name
f.close() # Allow tests to run on Windows
img = Image.from_file(name, format='gif')
assert img.format == 'gif'
def test_value_repr_length():
with get_logo_png() as LOGO_PNG:
with open(LOGO_PNG, 'rb') as f:
img = Image.from_file(f)
assert len(img.__repr__()) < 140
assert img.__repr__().endswith(")")
assert img.__repr__()[-5:-2] == '...'
def test_value_repr_url():
img = Image.from_url(b'https://jupyter.org/assets/main-logo.svg')
assert 'https://jupyter.org/assets/main-logo.svg' in img.__repr__()
# Helper functions
def get_hash_hex(byte_str):
m = hashlib.new('sha256')
m.update(byte_str)
return m.hexdigest()
def assert_equal_hash(byte_str, digest):
assert get_hash_hex(byte_str) == digest

View File

@@ -0,0 +1,94 @@
# coding: utf-8
# Copyright (c) Vidar Tonaas Fauske.
# Distributed under the terms of the Modified BSD License.
import pytest
import datetime
import pytz
from traitlets import TraitError
from ..widget_datetime import NaiveDatetimePicker
def test_time_creation_blank():
w = NaiveDatetimePicker()
assert w.value is None
def test_time_creation_value():
t = datetime.datetime.today()
w = NaiveDatetimePicker(value=t)
assert w.value is t
def test_time_validate_value_none():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7)
t_min = datetime.datetime(1442, 1, 1)
t_max = datetime.datetime(2056, 1, 1)
w = NaiveDatetimePicker(value=t, min=t_min, max=t_max)
w.value = None
assert w.value is None
def test_time_validate_value_vs_min():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7)
t_min = datetime.datetime(2019, 1, 1)
t_max = datetime.datetime(2056, 1, 1)
w = NaiveDatetimePicker(min=t_min, max=t_max)
w.value = t
assert w.value.year == 2019
def test_time_validate_value_vs_max():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7)
t_min = datetime.datetime(1664, 1, 1)
t_max = datetime.datetime(1994, 1, 1)
w = NaiveDatetimePicker(min=t_min, max=t_max)
w.value = t
assert w.value.year == 1994
def test_time_validate_min_vs_value():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7)
t_min = datetime.datetime(2019, 1, 1)
t_max = datetime.datetime(2056, 1, 1)
w = NaiveDatetimePicker(value=t, max=t_max)
w.min = t_min
assert w.value.year == 2019
def test_time_validate_min_vs_max():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7)
t_min = datetime.datetime(2112, 1, 1)
t_max = datetime.datetime(2056, 1, 1)
w = NaiveDatetimePicker(value=t, max=t_max)
with pytest.raises(TraitError):
w.min = t_min
def test_time_validate_max_vs_value():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7)
t_min = datetime.datetime(1664, 1, 1)
t_max = datetime.datetime(1994, 1, 1)
w = NaiveDatetimePicker(value=t, min=t_min)
w.max = t_max
assert w.value.year == 1994
def test_time_validate_max_vs_min():
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7)
t_min = datetime.datetime(1664, 1, 1)
t_max = datetime.datetime(1337, 1, 1)
w = NaiveDatetimePicker(value=t, min=t_min)
with pytest.raises(TraitError):
w.max = t_max
def test_datetime_tzinfo():
tz = pytz.timezone('Australia/Sydney')
t = datetime.datetime(2002, 2, 20, 13, 37, 42, 7, tzinfo=tz)
with pytest.raises(TraitError):
w = NaiveDatetimePicker(value=t)

View File

@@ -0,0 +1,239 @@
import sys
from unittest import TestCase
from contextlib import contextmanager
from IPython.display import Markdown, Image
from ipywidgets import widget_output
class TestOutputWidget(TestCase):
@contextmanager
def _mocked_ipython(self, get_ipython, clear_output):
""" Context manager that monkeypatches get_ipython and clear_output """
original_clear_output = widget_output.clear_output
original_get_ipython = widget_output.get_ipython
widget_output.get_ipython = get_ipython
widget_output.clear_output = clear_output
try:
yield
finally:
widget_output.clear_output = original_clear_output
widget_output.get_ipython = original_get_ipython
def _mock_get_ipython(self, msg_id):
""" Returns a mock IPython application with a mocked kernel """
kernel = type(
'mock_kernel',
(object, ),
{'_parent_header': {'header': {'msg_id': msg_id}}}
)
# Specifically override this so the traceback
# is still printed to screen
def showtraceback(self_, exc_tuple, *args, **kwargs):
etype, evalue, tb = exc_tuple
raise etype(evalue)
ipython = type(
'mock_ipython',
(object, ),
{'kernel': kernel, 'showtraceback': showtraceback}
)
return ipython
def _mock_clear_output(self):
""" Mock function that records calls to it """
calls = []
def clear_output(*args, **kwargs):
calls.append((args, kwargs))
clear_output.calls = calls
return clear_output
def test_set_msg_id_when_capturing(self):
msg_id = 'msg-id'
get_ipython = self._mock_get_ipython(msg_id)
clear_output = self._mock_clear_output()
with self._mocked_ipython(get_ipython, clear_output):
widget = widget_output.Output()
assert widget.msg_id == ''
with widget:
assert widget.msg_id == msg_id
assert widget.msg_id == ''
def test_clear_output(self):
msg_id = 'msg-id'
get_ipython = self._mock_get_ipython(msg_id)
clear_output = self._mock_clear_output()
with self._mocked_ipython(get_ipython, clear_output):
widget = widget_output.Output()
widget.clear_output(wait=True)
assert len(clear_output.calls) == 1
assert clear_output.calls[0] == ((), {'wait': True})
def test_capture_decorator(self):
msg_id = 'msg-id'
get_ipython = self._mock_get_ipython(msg_id)
clear_output = self._mock_clear_output()
expected_argument = 'arg'
expected_keyword_argument = True
captee_calls = []
with self._mocked_ipython(get_ipython, clear_output):
widget = widget_output.Output()
assert widget.msg_id == ''
@widget.capture()
def captee(*args, **kwargs):
# Check that we are capturing output
assert widget.msg_id == msg_id
# Check that arguments are passed correctly
captee_calls.append((args, kwargs))
captee(
expected_argument, keyword_argument=expected_keyword_argument)
assert widget.msg_id == ''
captee()
assert len(captee_calls) == 2
assert captee_calls[0] == (
(expected_argument, ),
{'keyword_argument': expected_keyword_argument}
)
assert captee_calls[1] == ((), {})
def test_capture_decorator_clear_output(self):
msg_id = 'msg-id'
get_ipython = self._mock_get_ipython(msg_id)
clear_output = self._mock_clear_output()
with self._mocked_ipython(get_ipython, clear_output):
widget = widget_output.Output()
@widget.capture(clear_output=True, wait=True)
def captee(*args, **kwargs):
# Check that we are capturing output
assert widget.msg_id == msg_id
captee()
captee()
assert len(clear_output.calls) == 2
assert clear_output.calls[0] == clear_output.calls[1] == \
((), {'wait': True})
def test_capture_decorator_no_clear_output(self):
msg_id = 'msg-id'
get_ipython = self._mock_get_ipython(msg_id)
clear_output = self._mock_clear_output()
with self._mocked_ipython(get_ipython, clear_output):
widget = widget_output.Output()
@widget.capture(clear_output=False)
def captee(*args, **kwargs):
# Check that we are capturing output
assert widget.msg_id == msg_id
captee()
captee()
assert len(clear_output.calls) == 0
def _make_stream_output(text, name):
return {
'output_type': 'stream',
'name': name,
'text': text
}
def test_append_stdout():
widget = widget_output.Output()
# Try appending a message to stdout.
widget.append_stdout("snakes!")
expected = (_make_stream_output("snakes!", "stdout"),)
assert widget.outputs == expected, repr(widget.outputs)
# Try appending a second message.
widget.append_stdout("more snakes!")
expected += (_make_stream_output("more snakes!", "stdout"),)
assert widget.outputs == expected, repr(widget.outputs)
def test_append_stderr():
widget = widget_output.Output()
# Try appending a message to stderr.
widget.append_stderr("snakes!")
expected = (_make_stream_output("snakes!", "stderr"),)
assert widget.outputs == expected, repr(widget.outputs)
# Try appending a second message.
widget.append_stderr("more snakes!")
expected += (_make_stream_output("more snakes!", "stderr"),)
assert widget.outputs == expected, repr(widget.outputs)
def test_append_display_data():
widget = widget_output.Output()
# Try appending a Markdown object.
widget.append_display_data(Markdown("# snakes!"))
expected = (
{
'output_type': 'display_data',
'data': {
'text/plain': '<IPython.core.display.Markdown object>',
'text/markdown': '# snakes!'
},
'metadata': {}
},
)
assert widget.outputs == expected, repr(widget.outputs)
# Now try appending an Image.
image_data = b"foobar"
widget.append_display_data(Image(image_data, width=123, height=456))
# Old ipykernel/IPython
expected1 = expected + (
{
'output_type': 'display_data',
'data': {
'image/png': 'Zm9vYmFy\n',
'text/plain': '<IPython.core.display.Image object>'
},
'metadata': {
'image/png': {
'width': 123,
'height': 456
}
}
},
)
# Latest ipykernel/IPython
expected2 = expected + (
{
'output_type': 'display_data',
'data': {
'image/png': 'Zm9vYmFy',
'text/plain': '<IPython.core.display.Image object>'
},
'metadata': {
'image/png': {
'width': 123,
'height': 456
}
}
},
)
assert widget.outputs == expected1 or widget.outputs == expected2

View File

@@ -0,0 +1,107 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import inspect
from unittest import TestCase
from traitlets import TraitError
from ipywidgets import Dropdown, SelectionSlider, Select
class TestDropdown(TestCase):
def test_construction(self):
Dropdown()
def test_dict_mapping_options(self):
d = Dropdown(options={'One': 1, 'Two': 2, 'Three': 3})
assert d.get_state('_options_labels') == {'_options_labels': ('One', 'Two', 'Three')}
def test_setting_options_from_list(self):
d = Dropdown()
assert d.options == ()
d.options = ['One', 'Two', 'Three']
assert d.get_state('_options_labels') == {'_options_labels': ('One', 'Two', 'Three')}
def test_setting_options_from_list_tuples(self):
d = Dropdown()
assert d.options == ()
d.options = [('One', 1), ('Two', 2), ('Three', 3)]
assert d.get_state('_options_labels') == {'_options_labels': ('One', 'Two', 'Three')}
d.value = 2
assert d.get_state('index') == {'index': 1}
def test_setting_options_from_dict(self):
d = Dropdown()
assert d.options == ()
d.options = {'One': 1, 'Two': 2, 'Three': 3}
assert d.get_state('_options_labels') == {'_options_labels': ('One', 'Two', 'Three')}
class TestSelectionSlider(TestCase):
def test_construction(self):
SelectionSlider(options=['a', 'b', 'c'])
def test_index_trigger(self):
slider = SelectionSlider(options=['a', 'b', 'c'])
observations = []
def f(change):
observations.append(change.new)
slider.observe(f, 'index')
assert slider.index == 0
slider.options = [4, 5, 6]
assert slider.index == 0
assert slider.value == 4
assert slider.label == '4'
assert observations == [0]
class TestSelection(TestCase):
def test_construction(self):
select = Select(options=['a', 'b', 'c'])
def test_index_trigger(self):
select = Select(options=[1, 2, 3])
observations = []
def f(change):
observations.append(change.new)
select.observe(f, 'index')
assert select.index == 0
select.options = [4, 5, 6]
assert select.index == 0
assert select.value == 4
assert select.label == '4'
assert observations == [0]
def test_duplicate(self):
select = Select(options=['first', 1, 'dup', 'dup'])
observations = []
def f(change):
observations.append(change.new)
select.observe(f, 'index')
select.index = 3
assert select.index == 3
assert select.value == 'dup'
assert select.label == 'dup'
assert observations == [3]
select.index = 2
assert select.index == 2
assert select.value == 'dup'
assert select.label == 'dup'
assert observations == [3, 2]
select.index = 0
assert select.index == 0
assert select.value == 'first'
assert select.label == 'first'
assert observations == [3, 2, 0]
# picks the first matching value
select.value = 'dup'
assert select.index == 2
assert select.value == 'dup'
assert select.label == 'dup'
assert observations == [3, 2, 0, 2]

View File

@@ -0,0 +1,65 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import inspect
import pytest
from ..widget_string import Combobox, Text
def test_combobox_creation_blank():
w = Combobox()
assert w.value == ''
assert w.options == ()
assert w.ensure_option == False
def test_combobox_creation_kwargs():
w = Combobox(
value='Chocolate',
options=[
"Chocolate",
"Coconut",
"Mint",
"Strawberry",
"Vanilla",
],
ensure_option=True
)
assert w.value == 'Chocolate'
assert w.options == (
"Chocolate",
"Coconut",
"Mint",
"Strawberry",
"Vanilla",
)
assert w.ensure_option == True
def test_tooltip_deprecation():
caller_path = inspect.stack(context=0)[1].filename
with pytest.deprecated_call() as record:
w = Text(description_tooltip="testing")
assert len(record) == 1
assert record[0].filename == caller_path
with pytest.deprecated_call() as record:
w.description_tooltip
assert len(record) == 1
assert record[0].filename == caller_path
with pytest.deprecated_call() as record:
w.description_tooltip == "testing"
assert len(record) == 1
assert record[0].filename == caller_path
with pytest.deprecated_call() as record:
w.description_tooltip = "second value"
assert len(record) == 1
assert record[0].filename == caller_path
assert w.tooltip == "second value"
def test_on_submit_deprecation():
with pytest.deprecated_call() as record:
Text().on_submit(lambda *args: ...)
assert len(record) == 1
assert record[0].filename == inspect.stack(context=0)[1].filename

View File

@@ -0,0 +1,714 @@
"Testing widget layout templates"
from unittest import TestCase
from unittest import mock
import pytest
import traitlets
import ipywidgets as widgets
from ipywidgets.widgets.widget_templates import LayoutProperties
class TestTwoByTwoLayout(TestCase):
"""test layout templates"""
def test_merge_cells(self): #pylint: disable=no-self-use
"""test merging cells with missing widgets"""
button1 = widgets.Button()
button2 = widgets.Button()
button3 = widgets.Button()
button4 = widgets.Button()
box = widgets.TwoByTwoLayout(top_left=button1,
top_right=button2,
bottom_left=button3,
bottom_right=button4)
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
'"bottom-left bottom-right"')
assert box.top_left.layout.grid_area == 'top-left'
assert box.top_right.layout.grid_area == 'top-right'
assert box.bottom_left.layout.grid_area == 'bottom-left'
assert box.bottom_right.layout.grid_area == 'bottom-right'
assert len(box.get_state()['children']) == 4
box = widgets.TwoByTwoLayout(top_left=button1,
top_right=button2,
bottom_left=None,
bottom_right=button4)
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
'"top-left bottom-right"')
assert box.top_left.layout.grid_area == 'top-left'
assert box.top_right.layout.grid_area == 'top-right'
assert box.bottom_left is None
assert box.bottom_right.layout.grid_area == 'bottom-right'
assert len(box.get_state()['children']) == 3
box = widgets.TwoByTwoLayout(top_left=None,
top_right=button2,
bottom_left=button3,
bottom_right=button4)
assert box.layout.grid_template_areas == ('"bottom-left top-right"\n' +
'"bottom-left bottom-right"')
assert box.top_left is None
assert box.top_right.layout.grid_area == 'top-right'
assert box.bottom_left.layout.grid_area == 'bottom-left'
assert box.bottom_right.layout.grid_area == 'bottom-right'
assert len(box.get_state()['children']) == 3
box = widgets.TwoByTwoLayout(top_left=None,
top_right=button2,
bottom_left=None,
bottom_right=button4)
assert box.layout.grid_template_areas == ('"top-right top-right"\n' +
'"bottom-right bottom-right"')
assert box.top_left is None
assert box.top_right.layout.grid_area == 'top-right'
assert box.bottom_left is None
assert box.bottom_right.layout.grid_area == 'bottom-right'
assert len(box.get_state()['children']) == 2
box = widgets.TwoByTwoLayout(top_left=button1,
top_right=None,
bottom_left=button3,
bottom_right=button4)
assert box.layout.grid_template_areas == ('"top-left bottom-right"\n' +
'"bottom-left bottom-right"')
assert box.top_left.layout.grid_area == 'top-left'
assert box.top_right is None
assert box.bottom_left.layout.grid_area == 'bottom-left'
assert box.bottom_right.layout.grid_area == 'bottom-right'
assert len(box.get_state()['children']) == 3
box = widgets.TwoByTwoLayout(top_left=button1,
top_right=None,
bottom_left=None,
bottom_right=None)
assert box.layout.grid_template_areas == ('"top-left top-left"\n' +
'"top-left top-left"')
assert box.top_left is button1
assert box.top_left.layout.grid_area == 'top-left'
assert box.top_right is None
assert box.bottom_left is None
assert box.bottom_right is None
assert len(box.get_state()['children']) == 1
box = widgets.TwoByTwoLayout(top_left=None,
top_right=button1,
bottom_left=None,
bottom_right=None)
assert box.layout.grid_template_areas == ('"top-right top-right"\n' +
'"top-right top-right"')
assert box.top_right is button1
assert box.top_right.layout.grid_area == 'top-right'
assert box.top_left is None
assert box.bottom_left is None
assert box.bottom_right is None
assert len(box.get_state()['children']) == 1
box = widgets.TwoByTwoLayout(top_left=None,
top_right=None,
bottom_left=None,
bottom_right=None)
assert box.layout.grid_template_areas is None
assert box.top_left is None
assert box.top_right is None
assert box.bottom_left is None
assert box.bottom_right is None
assert not box.get_state()['children']
box = widgets.TwoByTwoLayout(top_left=None,
top_right=button1,
bottom_left=None,
bottom_right=None,
merge=False)
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
'"bottom-left bottom-right"')
assert box.top_right is button1
assert box.top_right.layout.grid_area == 'top-right'
assert box.top_left is None
assert box.bottom_left is None
assert box.bottom_right is None
assert len(box.get_state()['children']) == 1
def test_keep_layout_options(self): #pylint: disable=no-self-use
"""test whether layout options are passed down to GridBox"""
layout = widgets.Layout(align_items="center")
button1 = widgets.Button()
button2 = widgets.Button()
button3 = widgets.Button()
button4 = widgets.Button()
box = widgets.TwoByTwoLayout(top_left=button1, top_right=button2,
bottom_left=button3, bottom_right=button4,
layout=layout)
assert box.layout.align_items == 'center'
def test_pass_layout_options(self): #pylint: disable=no-self-use
"""test whether the extra layout options of the template class are
passed down to Layout object"""
button1 = widgets.Button()
button2 = widgets.Button()
button3 = widgets.Button()
button4 = widgets.Button()
box = widgets.TwoByTwoLayout(top_left=button1, top_right=button2,
bottom_left=button3, bottom_right=button4,
grid_gap="10px", justify_content="center",
align_items="center")
assert box.layout.grid_gap == "10px"
assert box.layout.justify_content == "center"
assert box.layout.align_items == "center"
# we still should be able to pass them through layout
layout = widgets.Layout(grid_gap="10px", justify_content="center",
align_items="center")
box = widgets.TwoByTwoLayout(top_left=button1, top_right=button2,
bottom_left=button3, bottom_right=button4,
layout=layout
)
assert box.layout.grid_gap == "10px"
assert box.layout.justify_content == "center"
assert box.layout.align_items == "center"
# values passed directly in the constructor should overwrite layout options
layout = widgets.Layout(grid_gap="10px", justify_content="center",
align_items="center")
box = widgets.TwoByTwoLayout(top_left=button1, top_right=button2,
bottom_left=button3, bottom_right=button4,
layout=layout, grid_gap="30px"
)
assert box.layout.grid_gap == "30px"
assert box.layout.justify_content == "center"
assert box.layout.align_items == "center"
@mock.patch("ipywidgets.Layout.send_state")
def test_update_dynamically(self, send_state): #pylint: disable=no-self-use
"""test whether it's possible to add widget outside __init__"""
button1 = widgets.Button()
button2 = widgets.Button()
button3 = widgets.Button()
button4 = widgets.Button()
box = widgets.TwoByTwoLayout(top_left=button1, top_right=button3,
bottom_left=None, bottom_right=button4)
from ipykernel.kernelbase import Kernel
state = box.get_state()
assert len(state['children']) == 3
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
'"top-left bottom-right"')
box.layout.comm.kernel = mock.MagicMock(spec=Kernel) #for mocking purposes
send_state.reset_mock()
box.bottom_left = button2
state = box.get_state()
assert len(state['children']) == 4
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
'"bottom-left bottom-right"')
# check whether frontend was informed
send_state.assert_called_with(key="grid_template_areas")
box = widgets.TwoByTwoLayout(top_left=button1, top_right=button3,
bottom_left=None, bottom_right=button4)
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
'"top-left bottom-right"')
box.layout.comm.kernel = mock.MagicMock(spec=Kernel) #for mocking purposes
send_state.reset_mock()
box.merge = False
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
'"bottom-left bottom-right"')
send_state.assert_called_with(key="grid_template_areas")
class TestAppLayout(TestCase):
"""test layout templates"""
def test_create_with_defaults(self):
"test creating with default values"
footer = widgets.Button()
header = widgets.Button()
center = widgets.Button()
left_sidebar = widgets.Button()
right_sidebar = widgets.Button()
box = widgets.AppLayout(
footer=footer,
header=header,
center=center,
left_sidebar=left_sidebar,
right_sidebar=right_sidebar
)
assert box.layout.grid_template_areas == ('"header header header"\n' +
'"left-sidebar center right-sidebar"\n' +
'"footer footer footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.center.layout.grid_area == 'center'
assert box.left_sidebar.layout.grid_area == 'left-sidebar'
assert box.right_sidebar.layout.grid_area == 'right-sidebar'
assert len(box.get_state()['children']) == 5
# empty layout should produce no effects
box = widgets.AppLayout()
assert box.layout.grid_template_areas is None
assert box.layout.grid_template_columns is None
assert box.layout.grid_template_rows is None
assert len(box.get_state()['children']) == 0
def test_merge_empty_cells(self):
"test if cells are correctly merged"
footer = widgets.Button()
header = widgets.Button()
center = widgets.Button()
left_sidebar = widgets.Button()
right_sidebar = widgets.Button()
# merge all if only one widget
box = widgets.AppLayout(
center=center
)
assert box.layout.grid_template_areas == ('"center center center"\n' +
'"center center center"\n' +
'"center center center"')
assert box.center.layout.grid_area == 'center'
assert len(box.get_state()['children']) == 1
box = widgets.AppLayout(
left_sidebar=left_sidebar
)
assert box.layout.grid_template_areas == ('"left-sidebar left-sidebar left-sidebar"\n' +
'"left-sidebar left-sidebar left-sidebar"\n' +
'"left-sidebar left-sidebar left-sidebar"')
assert box.left_sidebar.layout.grid_area == 'left-sidebar'
assert len(box.get_state()['children']) == 1
# merge left and right sidebars with center
box = widgets.AppLayout(
header=header,
footer=footer,
left_sidebar=left_sidebar,
center=center
)
assert box.layout.grid_template_areas == ('"header header header"\n' +
'"left-sidebar center center"\n' +
'"footer footer footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.center.layout.grid_area == 'center'
assert box.left_sidebar.layout.grid_area == 'left-sidebar'
assert len(box.get_state()['children']) == 4
box = widgets.AppLayout(
header=header,
footer=footer,
right_sidebar=right_sidebar,
center=center
)
assert box.layout.grid_template_areas == ('"header header header"\n' +
'"center center right-sidebar"\n' +
'"footer footer footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.center.layout.grid_area == 'center'
assert box.right_sidebar.layout.grid_area == 'right-sidebar'
assert len(box.get_state()['children']) == 4
box = widgets.AppLayout(
header=header,
footer=footer,
center=center
)
assert box.layout.grid_template_areas == ('"header header header"\n' +
'"center center center"\n' +
'"footer footer footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.center.layout.grid_area == 'center'
assert len(box.get_state()['children']) == 3
# if only center missing, remove it from view
box = widgets.AppLayout(
header=header,
footer=footer,
center=None,
left_sidebar=left_sidebar,
right_sidebar=right_sidebar
)
assert box.layout.grid_template_areas == ('"header header"\n' +
'"left-sidebar right-sidebar"\n' +
'"footer footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.left_sidebar.layout.grid_area == 'left-sidebar'
assert box.right_sidebar.layout.grid_area == 'right-sidebar'
assert box.center is None
assert len(box.get_state()['children']) == 4
# center and one sidebar missing -> 3 row arrangement
box = widgets.AppLayout(
header=header,
footer=footer,
center=None,
left_sidebar=None,
right_sidebar=right_sidebar
)
assert box.layout.grid_template_areas == ('"header header"\n' +
'"right-sidebar right-sidebar"\n' +
'"footer footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.left_sidebar is None
assert box.right_sidebar.layout.grid_area == 'right-sidebar'
assert box.center is None
assert len(box.get_state()['children']) == 3
# remove middle row is both sidebars and center missing
box = widgets.AppLayout(
header=header,
footer=footer,
center=None,
left_sidebar=None,
right_sidebar=None
)
assert box.layout.grid_template_areas == ('"header"\n' +
'"footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.center is None
assert box.left_sidebar is None
assert box.right_sidebar is None
assert len(box.get_state()['children']) == 2
# do not merge if merge=False
box = widgets.AppLayout(
header=header,
footer=footer,
center=center,
merge=False
)
assert box.layout.grid_template_areas == ('"header header header"\n' +
'"left-sidebar center right-sidebar"\n' +
'"footer footer footer"')
assert box.footer.layout.grid_area == 'footer'
assert box.header.layout.grid_area == 'header'
assert box.center.layout.grid_area == 'center'
assert box.left_sidebar is None
assert box.right_sidebar is None
assert len(box.get_state()['children']) == 3
# merge header and footer simply removes it from view
box = widgets.AppLayout(
footer=footer,
center=center,
left_sidebar=left_sidebar,
right_sidebar=right_sidebar
)
assert box.layout.grid_template_areas == ('"left-sidebar center right-sidebar"\n' +
'"footer footer footer"')
assert box.center.layout.grid_area == 'center'
assert box.left_sidebar.layout.grid_area == 'left-sidebar'
assert box.right_sidebar.layout.grid_area == 'right-sidebar'
assert box.footer.layout.grid_area == 'footer'
assert box.header is None
assert len(box.get_state()['children']) == 4
box = widgets.AppLayout(
header=header,
center=center,
left_sidebar=left_sidebar,
right_sidebar=right_sidebar
)
assert box.layout.grid_template_areas == ('"header header header"\n' +
'"left-sidebar center right-sidebar"')
assert box.center.layout.grid_area == 'center'
assert box.left_sidebar.layout.grid_area == 'left-sidebar'
assert box.right_sidebar.layout.grid_area == 'right-sidebar'
assert box.header.layout.grid_area == 'header'
assert box.footer is None
assert len(box.get_state()['children']) == 4
box = widgets.AppLayout(
center=center,
left_sidebar=left_sidebar,
right_sidebar=right_sidebar
)
assert box.layout.grid_template_areas == '"left-sidebar center right-sidebar"'
assert box.center.layout.grid_area == 'center'
assert box.left_sidebar.layout.grid_area == 'left-sidebar'
assert box.right_sidebar.layout.grid_area == 'right-sidebar'
assert box.footer is None
assert box.header is None
assert len(box.get_state()['children']) == 3
# merge all if only one widget
box = widgets.AppLayout(
center=center
)
assert box.layout.grid_template_areas == ('"center center center"\n' +
'"center center center"\n' +
'"center center center"')
assert box.center.layout.grid_area == 'center'
assert len(box.get_state()['children']) == 1
def test_size_to_css(self):
box = widgets.AppLayout()
assert box._size_to_css("100px") == '100px'
assert box._size_to_css("1fr") == '1fr'
assert box._size_to_css("2.5fr") == '2.5fr'
assert box._size_to_css('2.5') == '2.5fr'
assert box._size_to_css('25%') == '25%'
with pytest.raises(TypeError):
box._size_to_css('this is not correct size')
def test_set_pane_widths_heights(self):
footer = widgets.Button()
header = widgets.Button()
center = widgets.Button()
left_sidebar = widgets.Button()
right_sidebar = widgets.Button()
box = widgets.AppLayout(
header=header,
footer=footer,
left_sidebar=left_sidebar,
right_sidebar=left_sidebar,
center=center
)
with pytest.raises(traitlets.TraitError):
box.pane_widths = ['1fx', '1fx', '1fx', '1fx']
with pytest.raises(traitlets.TraitError):
box.pane_widths = ['1fx', '1fx']
with pytest.raises(traitlets.TraitError):
box.pane_heights = ['1fx', '1fx', '1fx', '1fx']
with pytest.raises(traitlets.TraitError):
box.pane_heights = ['1fx', '1fx']
assert box.layout.grid_template_rows == "1fr 3fr 1fr"
assert box.layout.grid_template_columns == "1fr 2fr 1fr"
box.pane_heights = ['3fr', '100px', 20]
assert box.layout.grid_template_rows == "3fr 100px 20fr"
assert box.layout.grid_template_columns == "1fr 2fr 1fr"
box.pane_widths = [3, 3, 1]
assert box.layout.grid_template_rows == "3fr 100px 20fr"
assert box.layout.grid_template_columns == "3fr 3fr 1fr"
class TestGridspecLayout(TestCase):
"test GridspecLayout"
def test_init(self):
with pytest.raises(traitlets.TraitError):
box = widgets.GridspecLayout()
with pytest.raises(traitlets.TraitError):
box = widgets.GridspecLayout(n_rows=-1, n_columns=1)
box = widgets.GridspecLayout(n_rows=5, n_columns=3)
assert box.n_rows == 5
assert box.n_columns == 3
assert len(box._grid_template_areas) == 5
assert len(box._grid_template_areas[0]) == 3
box = widgets.GridspecLayout(1, 2)
assert box.n_rows == 1
assert box.n_columns == 2
with pytest.raises(traitlets.TraitError):
box = widgets.GridspecLayout(0, 0)
def test_setitem_index(self):
box = widgets.GridspecLayout(2, 3)
button1 = widgets.Button()
button2 = widgets.Button()
button3 = widgets.Button()
button4 = widgets.Button()
box[0, 0] = button1
button1_label = button1.layout.grid_area
assert button1 in box.children
assert box.layout.grid_template_areas == '''"{} . ."\n". . ."'''.format(button1_label)
box[-1, -1] = button2
button2_label = button2.layout.grid_area
assert button1_label != button2_label
assert button2 in box.children
assert box.layout.grid_template_areas == '''"{} . ."\n". . {}"'''.format(button1_label,
button2_label)
box[1, 0] = button3
button3_label = button3.layout.grid_area
assert button1_label != button3_label
assert button2_label != button3_label
assert button3 in box.children
assert box.layout.grid_template_areas == '''"{b1} . ."\n"{b3} . {b2}"'''.format(b1=button1_label,
b2=button2_label,
b3=button3_label)
#replace widget
box[1, 0] = button4
button4_label = button4.layout.grid_area
assert button1_label != button4_label
assert button2_label != button4_label
assert button4 in box.children
assert button3 not in box.children
assert box.layout.grid_template_areas == '''"{b1} . ."\n"{b4} . {b2}"'''.format(b1=button1_label,
b2=button2_label,
b4=button4_label)
def test_setitem_slices(self):
box = widgets.GridspecLayout(2, 3)
button1 = widgets.Button()
box[:2, 0] = button1
assert len(box.children) == 1
assert button1 in box.children
button1_label = button1.layout.grid_area
assert box.layout.grid_template_areas == '''"{b1} . ."\n"{b1} . ."'''.format(b1=button1_label)
box = widgets.GridspecLayout(2, 3)
button1 = widgets.Button()
button2 = widgets.Button()
box[:2, 1:] = button1
assert len(box.children) == 1
assert button1 in box.children
button1_label = button1.layout.grid_area
assert box.layout.grid_template_areas == '''". {b1} {b1}"\n". {b1} {b1}"'''.format(b1=button1_label)
# replace button
box[:2, 1:] = button2
assert len(box.children) == 1
assert button2 in box.children
button2_label = button2.layout.grid_area
assert box.layout.grid_template_areas == '''". {b1} {b1}"\n". {b1} {b1}"'''.format(b1=button2_label)
def test_getitem_index(self):
"test retrieving widget"
box = widgets.GridspecLayout(2, 3)
button1 = widgets.Button()
box[0, 0] = button1
assert box[0, 0] is button1
def test_getitem_slices(self):
"test retrieving widgets with slices"
box = widgets.GridspecLayout(2, 3)
button1 = widgets.Button()
box[:2, 0] = button1
assert box[:2, 0] is button1
box = widgets.GridspecLayout(2, 3)
button1 = widgets.Button()
button2 = widgets.Button()
box[0, 0] = button1
box[1, 0] = button2
assert box[0, 0] is button1
assert box[1, 0] is button2
with pytest.raises(TypeError, match="The slice spans"):
button = box[:2, 0]
class TestLayoutProperties(TestCase):
"""test mixin with layout properties"""
class DummyTemplate(widgets.GridBox, LayoutProperties):
location = traitlets.Instance(widgets.Widget, allow_none=True)
def test_layout_updated_on_trait_change(self):
"test whether respective layout traits are updated when traits change"
template = self.DummyTemplate(width="100%")
assert template.width == '100%'
assert template.layout.width == '100%'
template.width = 'auto'
assert template.width == 'auto'
assert template.layout.width == 'auto'
def test_align_items_extra_options(self):
template = self.DummyTemplate(align_items='top')
assert template.align_items == 'top'
assert template.layout.align_items == 'flex-start'
template.align_items = 'bottom'
assert template.align_items == 'bottom'
assert template.layout.align_items == 'flex-end'
def test_validate_properties(self):
prop_obj = self.DummyTemplate()
for prop in LayoutProperties.align_items.values:
prop_obj.align_items = prop
assert prop_obj.align_items == prop
with pytest.raises(traitlets.TraitError):
prop_obj.align_items = 'any default position'

View File

@@ -0,0 +1,100 @@
# coding: utf-8
# Copyright (c) Vidar Tonaas Fauske.
# Distributed under the terms of the Modified BSD License.
import pytest
import datetime
from traitlets import TraitError
from ..widget_time import TimePicker
def test_time_creation_blank():
w = TimePicker()
assert w.value is None
def test_time_creation_value():
t = datetime.time()
w = TimePicker(value=t)
assert w.value is t
def test_time_cross_validate_value_min_max():
w = TimePicker(value=datetime.time(2), min=datetime.time(2), max=datetime.time(2))
with w.hold_trait_notifications():
w.value = None
w.min = datetime.time(4)
w.max = datetime.time(6)
assert w.value is None
with w.hold_trait_notifications():
w.value = datetime.time(4)
w.min = None
w.max = None
assert w.value == datetime.time(4)
def test_time_validate_value_none():
t = datetime.time(13, 37, 42, 7)
t_min = datetime.time(2)
t_max = datetime.time(22)
w = TimePicker(value=t, min=t_min, max=t_max)
w.value = None
assert w.value is None
def test_time_validate_value_vs_min():
t = datetime.time(13, 37, 42, 7)
t_min = datetime.time(14)
t_max = datetime.time(22)
w = TimePicker(min=t_min, max=t_max)
w.value = t
assert w.value.hour == 14
def test_time_validate_value_vs_max():
t = datetime.time(13, 37, 42, 7)
t_min = datetime.time(2)
t_max = datetime.time(12)
w = TimePicker(min=t_min, max=t_max)
w.value = t
assert w.value.hour == 12
def test_time_validate_min_vs_value():
t = datetime.time(13, 37, 42, 7)
t_min = datetime.time(14)
t_max = datetime.time(22)
w = TimePicker(value=t, max=t_max)
w.min = t_min
assert w.value.hour == 14
def test_time_validate_min_vs_max():
t = datetime.time(13, 37, 42, 7)
t_min = datetime.time(14)
t_max = datetime.time(12)
w = TimePicker(value=t, max=t_max)
with pytest.raises(TraitError):
w.min = t_min
def test_time_validate_max_vs_value():
t = datetime.time(13, 37, 42, 7)
t_min = datetime.time(2)
t_max = datetime.time(12)
w = TimePicker(value=t, min=t_min)
w.max = t_max
assert w.value.hour == 12
def test_time_validate_max_vs_min():
t = datetime.time(13, 37, 42, 7)
t_min = datetime.time(2)
t_max = datetime.time(1)
w = TimePicker(value=t, min=t_min)
with pytest.raises(TraitError):
w.max = t_max

View File

@@ -0,0 +1,118 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import datetime as dt
from unittest import TestCase
from unittest.mock import MagicMock
from traitlets import TraitError
from ipywidgets import FileUpload
FILE_UPLOAD_FRONTEND_CONTENT = {
'name': 'file-name.txt',
'type': 'text/plain',
'size': 20760,
'last_modified': 1578578296434,
'content': memoryview(b'file content'),
}
class TestFileUpload(TestCase):
def test_construction(self):
uploader = FileUpload()
# Default
assert uploader.accept == ''
assert not uploader.multiple
assert not uploader.disabled
def test_construction_with_params(self):
uploader = FileUpload(
accept='.txt', multiple=True, disabled=True)
assert uploader.accept == '.txt'
assert uploader.multiple
assert uploader.disabled
def test_empty_initial_value(self):
uploader = FileUpload()
assert uploader.value == ()
def test_receive_single_file(self):
uploader = FileUpload()
message = {'value': [FILE_UPLOAD_FRONTEND_CONTENT]}
uploader.set_state(message)
assert len(uploader.value) == 1
(uploaded_file,) = uploader.value
assert uploaded_file.name == 'file-name.txt'
assert uploaded_file.type == 'text/plain'
assert uploaded_file.size == 20760
assert uploaded_file.content.tobytes() == b'file content'
assert (
uploaded_file.last_modified ==
dt.datetime(2020, 1, 9, 13, 58, 16, 434000, tzinfo=dt.timezone.utc)
)
def test_receive_multiple_files(self):
uploader = FileUpload(multiple=True)
message = {
'value': [
FILE_UPLOAD_FRONTEND_CONTENT,
{**FILE_UPLOAD_FRONTEND_CONTENT, **{'name': 'other-file-name.txt'}}
]
}
uploader.set_state(message)
assert len(uploader.value) == 2
assert uploader.value[0].name == 'file-name.txt'
assert uploader.value[1].name == 'other-file-name.txt'
def test_serialization_deserialization_integrity(self):
# The value traitlet needs to remain unchanged following
# a serialization / deserialization roundtrip, otherwise
# the kernel dispatches it back to the frontend following
# a state change, because it doesn't recognize that the
# property_lock entry is the same as the new value.
from ipykernel.comm import Comm
uploader = FileUpload()
mock_comm = MagicMock(spec=Comm)
mock_comm.send = MagicMock()
mock_comm.kernel = 'does not matter'
uploader.comm = mock_comm
message = {'value': [FILE_UPLOAD_FRONTEND_CONTENT]}
uploader.set_state(message)
# Check that no message is sent back to the frontend
# as a result of setting the state.
mock_comm.send.assert_not_called()
def test_resetting_value(self):
# Simulate an upload, then resetting the value from the
# kernel.
uploader = FileUpload()
message = {'value': [FILE_UPLOAD_FRONTEND_CONTENT]}
uploader.set_state(message)
uploader.value = [] # reset value to an empty file list
assert uploader.get_state(key='value') == {'value': []}
def test_setting_non_empty_value(self):
# Simulate user setting a value for the upload from the kernel.
uploader = FileUpload()
content = memoryview(b'some content')
uploader.value = [{
'name': 'some-name.txt',
'type': 'text/plain',
'size': 561,
'last_modified': dt.datetime(2020, 1, 9, 13, 58, 16, 434000, tzinfo=dt.timezone.utc),
'content': content
}]
state = uploader.get_state(key='value')
assert len(state['value']) == 1
[entry] = state['value']
assert entry['name'] == 'some-name.txt'
assert entry['type'] == 'text/plain'
assert entry['size'] == 561
assert entry['last_modified'] == 1578578296434
assert entry['content'] == content

View File

@@ -0,0 +1,97 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from ipywidgets import Widget
import ipywidgets.widgets.widget
# The new comm package is not available in our Python 3.7 CI (older ipykernel version)
try:
import comm
NEW_COMM_PACKAGE = True
except ImportError:
NEW_COMM_PACKAGE = False
import ipykernel.comm
import pytest
class DummyComm():
comm_id = 'a-b-c-d'
kernel = 'Truthy'
def __init__(self, *args, **kwargs):
super().__init__()
self.messages = []
def open(self, *args, **kwargs):
pass
def on_msg(self, *args, **kwargs):
pass
def send(self, *args, **kwargs):
self.messages.append((args, kwargs))
def close(self, *args, **kwargs):
pass
def dummy_create_comm(**kwargs):
return DummyComm()
def dummy_get_comm_manager(**kwargs):
return {}
_widget_attrs = {}
undefined = object()
if NEW_COMM_PACKAGE:
orig_comm = ipykernel.comm.comm.BaseComm
else:
orig_comm = ipykernel.comm.Comm
orig_create_comm = None
orig_get_comm_manager = None
if NEW_COMM_PACKAGE:
orig_create_comm = comm.create_comm
orig_get_comm_manager = comm.get_comm_manager
def setup_test_comm():
if NEW_COMM_PACKAGE:
comm.create_comm = dummy_create_comm
comm.get_comm_manager = dummy_get_comm_manager
ipykernel.comm.comm.BaseComm = DummyComm
else:
ipykernel.comm.Comm = DummyComm
Widget.comm.klass = DummyComm
ipywidgets.widgets.widget.Comm = DummyComm
_widget_attrs['_repr_mimebundle_'] = Widget._repr_mimebundle_
def raise_not_implemented(*args, **kwargs):
raise NotImplementedError()
Widget._repr_mimebundle_ = raise_not_implemented
def teardown_test_comm():
if NEW_COMM_PACKAGE:
comm.create_comm = orig_create_comm
comm.get_comm_manager = orig_get_comm_manager
ipykernel.comm.comm.BaseComm = orig_comm
else:
ipykernel.comm.Comm = orig_comm
Widget.comm.klass = orig_comm
ipywidgets.widgets.widget.Comm = orig_comm
for attr, value in _widget_attrs.items():
if value is undefined:
delattr(Widget, attr)
else:
setattr(Widget, attr, value)
_widget_attrs.clear()
@pytest.fixture(autouse=True)
def setup():
setup_test_comm()
yield
teardown_test_comm()
def call_method(method, *args, **kwargs):
method(*args, **kwargs)