Skip to content

Commit

Permalink
Revert to working tests
Browse files Browse the repository at this point in the history
  • Loading branch information
martinRenou committed Oct 21, 2024
1 parent 2c39710 commit c404349
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 197 deletions.
19 changes: 1 addition & 18 deletions ipywidgets/widgets/tests/test_send_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@

from traitlets import Bool, Tuple, List

from .utils import setup, teardown, DummyComm
from .utils import setup, teardown

from ..widget import Widget

from ..._version import __control_protocol_version__

import ipykernel.ipkernel

# A widget with simple traits
class SimpleWidget(Widget):
a = Bool().tag(sync=True)
Expand All @@ -27,16 +23,3 @@ def test_empty_hold_sync():
with w.hold_sync():
pass
assert w.comm.messages == []


def test_control():
comm = DummyComm()
Widget.close_all()
w = SimpleWidget()
Widget.handle_control_comm_opened(
comm, dict(metadata={'version': __control_protocol_version__})
)
Widget._handle_control_comm_msg(dict(content=dict(
data={'method': 'request_states'}
)))
assert comm.messages
216 changes: 37 additions & 179 deletions ipywidgets/widgets/tests/test_set_state.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

from ipython_genutils.py3compat import PY3

import pytest
try:
from unittest import mock
except ImportError:
import mock


from traitlets import Bool, Tuple, List, Instance, CFloat, CInt, Float, Int, TraitError, observe

from .utils import setup, teardown

import ipywidgets
from ipywidgets import Widget
import ipykernel.ipkernel


@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
from ..widget import Widget

#
# First some widgets to test on:
Expand Down Expand Up @@ -71,93 +64,81 @@ 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)
d = Instance(DataInstance).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)
d = Instance(DataInstance).tag(sync=True, to_json=bytes_serializer, from_json=truncate_deserializer)


#
# Actual tests:
#

def test_set_state_simple(echo):
def test_set_state_simple():
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)
assert w.comm.messages == []


def test_set_state_transformer(echo):
def test_set_state_transformer():
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(
assert w.comm.messages == [((), dict(
buffers=[],
data=dict(
buffer_paths=[],
method='update',
state=dict(d=[False, True, False]),
))))
assert w.comm.messages == expected
state=dict(d=[False, True, False])
)))]


def test_set_state_data(echo):
def test_set_state_data():
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)
assert w.comm.messages == []


def test_set_state_data_truncate(echo):
def test_set_state_data_truncate():
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 len(w.comm.messages) == 1 # ensure we didn't get more than expected
msg = w.comm.messages[0]
# Assert that the data update (truncation) sends an update
buffers = msg[1].pop('buffers')
assert msg == ((), dict(
data=dict(
buffer_paths=[['d', 'data']],
method='update',
state=dict(d={}),
buffer_paths=[['d', 'data']]
state=dict(d={})
)))

# Sanity:
assert len(buffers) == 1
assert buffers[0] == data[:20].tobytes()


def test_set_state_numbers_int(echo):
def test_set_state_numbers_int():
# JS does not differentiate between float/int.
# Instead, it formats exact floats as ints in JSON (1.0 -> '1').

Expand All @@ -169,43 +150,43 @@ def test_set_state_numbers_int(echo):
i = 3,
ci = 4,
))
# Ensure one update message gets produced
assert len(w.comm.messages) == (1 if echo else 0)
# Ensure no update message gets produced
assert len(w.comm.messages) == 0


def test_set_state_numbers_float(echo):
def test_set_state_numbers_float():
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)
# Ensure no update message gets produced
assert len(w.comm.messages) == 0


def test_set_state_float_to_float(echo):
def test_set_state_float_to_float():
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)
# Ensure no update message gets produced
assert len(w.comm.messages) == 0


def test_set_state_cint_to_float(echo):
def test_set_state_cint_to_float():
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]
assert len(w.comm.messages) == 1
msg = w.comm.messages[0]
data = msg[1]['data']
assert data['method'] == 'update'
assert data['state'] == {'ci': 5}
Expand All @@ -227,7 +208,7 @@ def _x_test_set_state_int_to_int_like():
assert len(w.comm.messages) == 0


def test_set_state_int_to_float(echo):
def test_set_state_int_to_float():
w = NumberWidget()

# Set Int to float
Expand All @@ -236,7 +217,7 @@ def test_set_state_int_to_float(echo):
i = 3.5
))

def test_property_lock(echo):
def test_property_lock():
# 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)
Expand All @@ -260,136 +241,13 @@ def _propagate_value(self, change):
# 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:
with self.hold_sync():
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, 'other': 11.0}, 'buffer_paths': []}
# we expect first the {'value': 2.0} state to be send, followed by the {'value': 42.0} state
msg = {'method': 'update', 'state': {'value': 2.0}, 'buffer_paths': []}
call2 = mock.call(msg, buffers=[])

calls = [call42, call2] if echo else [call2]
widget._send.assert_has_calls(calls)


def test_echo(echo):
# we always echo values back to the frontend if configured
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': []}
msg = {'method': 'update', 'state': {'value': 42.0}, 'buffer_paths': []}
call42 = mock.call(msg, buffers=[])

calls = [call42] if echo else []
calls = [call2, call42]
widget._send.assert_has_calls(calls)



def test_echo_single(echo):
# 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] if echo else [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=[])])



0 comments on commit c404349

Please sign in to comment.