@@ -80,7 +80,7 @@ def test_set_state_simple():
8080 c = [False , True , False ],
8181 ))
8282
83- assert w .comm .messages == []
83+ assert len ( w .comm .messages ) == 1
8484
8585
8686def test_set_state_transformer ():
@@ -94,7 +94,8 @@ def test_set_state_transformer():
9494 data = dict (
9595 buffer_paths = [],
9696 method = 'update' ,
97- state = dict (d = [False , True , False ])
97+ state = dict (d = [False , True , False ]),
98+ echo = ['d' ],
9899 )))]
99100
100101
@@ -105,7 +106,7 @@ def test_set_state_data():
105106 a = True ,
106107 d = {'data' : data },
107108 ))
108- assert w .comm .messages == []
109+ assert len ( w .comm .messages ) == 1
109110
110111
111112def test_set_state_data_truncate ():
@@ -122,9 +123,10 @@ def test_set_state_data_truncate():
122123 buffers = msg [1 ].pop ('buffers' )
123124 assert msg == ((), dict (
124125 data = dict (
125- buffer_paths = [['d' , 'data' ]],
126126 method = 'update' ,
127- state = dict (d = {})
127+ state = dict (d = {}, a = True ),
128+ buffer_paths = [['d' , 'data' ]],
129+ echo = ['a' , 'd' ],
128130 )))
129131
130132 # Sanity:
@@ -144,8 +146,8 @@ def test_set_state_numbers_int():
144146 i = 3 ,
145147 ci = 4 ,
146148 ))
147- # Ensure no update message gets produced
148- assert len (w .comm .messages ) == 0
149+ # Ensure one update message gets produced
150+ assert len (w .comm .messages ) == 1
149151
150152
151153def test_set_state_numbers_float ():
@@ -156,8 +158,8 @@ def test_set_state_numbers_float():
156158 cf = 2.0 ,
157159 ci = 4.0
158160 ))
159- # Ensure no update message gets produced
160- assert len (w .comm .messages ) == 0
161+ # Ensure one update message gets produced
162+ assert len (w .comm .messages ) == 1
161163
162164
163165def test_set_state_float_to_float ():
@@ -167,8 +169,8 @@ def test_set_state_float_to_float():
167169 f = 1.2 ,
168170 cf = 2.6 ,
169171 ))
170- # Ensure no update message gets produced
171- assert len (w .comm .messages ) == 0
172+ # Ensure one message gets produced
173+ assert len (w .comm .messages ) == 1
172174
173175
174176def test_set_state_cint_to_float ():
@@ -235,6 +237,7 @@ def _propagate_value(self, change):
235237 # this mimics a value coming from the front end
236238 widget .set_state ({'value' : 42 })
237239 assert widget .value == 42
240+ assert widget .stop is True
238241
239242 # we expect no new state to be sent
240243 calls = []
@@ -263,8 +266,96 @@ def _propagate_value(self, change):
263266 assert widget .other == 11
264267
265268 # we expect only single state to be sent, i.e. the {'value': 42.0} state
266- msg = {'method' : 'update' , 'state' : {'value' : 2.0 , 'other' : 11.0 }, 'buffer_paths' : []}
269+ msg = {'method' : 'update' , 'state' : {'value' : 2.0 , 'other' : 11.0 }, 'buffer_paths' : [], 'echo' : [ 'value' ] }
267270 call42 = mock .call (msg , buffers = [])
268271
269272 calls = [call42 ]
270273 widget ._send .assert_has_calls (calls )
274+
275+
276+
277+ def test_echo ():
278+ # we always echo values back to the frontend
279+ class ValueWidget (Widget ):
280+ value = Float ().tag (sync = True )
281+
282+ widget = ValueWidget (value = 1 )
283+ assert widget .value == 1
284+
285+ widget ._send = mock .MagicMock ()
286+ # this mimics a value coming from the front end
287+ widget .set_state ({'value' : 42 })
288+ assert widget .value == 42
289+
290+ # we expect this to be echoed
291+ msg = {'method' : 'update' , 'state' : {'value' : 42.0 }, 'buffer_paths' : [], 'echo' : ['value' ]}
292+ call42 = mock .call (msg , buffers = [])
293+
294+ calls = [call42 ]
295+ widget ._send .assert_has_calls (calls )
296+
297+
298+ def test_echo_single ():
299+ # we always echo multiple changes back in 1 update
300+ class ValueWidget (Widget ):
301+ value = Float ().tag (sync = True )
302+ square = Float ().tag (sync = True )
303+ @observe ('value' )
304+ def _square (self , change ):
305+ self .square = self .value ** 2
306+
307+ widget = ValueWidget (value = 1 )
308+ assert widget .value == 1
309+
310+ widget ._send = mock .MagicMock ()
311+ # this mimics a value coming from the front end
312+ widget ._handle_msg ({
313+ 'content' : {
314+ 'data' : {
315+ 'method' : 'update' ,
316+ 'state' : {
317+ 'value' : 8 ,
318+ }
319+ }
320+ }
321+ })
322+ assert widget .value == 8
323+ assert widget .square == 64
324+
325+ # we expect this to be echoed
326+ # note that only value is echoed, not square
327+ msg = {'method' : 'update' , 'state' : {'square' : 64 , 'value' : 8.0 }, 'buffer_paths' : [], 'echo' : ['value' ]}
328+ call = mock .call (msg , buffers = [])
329+
330+ calls = [call ]
331+ widget ._send .assert_has_calls (calls )
332+
333+
334+ def test_no_echo ():
335+ # in cases where values coming fromt the frontend are 'heavy', we might want to opt out
336+ class ValueWidget (Widget ):
337+ value = Float ().tag (sync = True , no_echo = True )
338+
339+ widget = ValueWidget (value = 1 )
340+ assert widget .value == 1
341+
342+ widget ._send = mock .MagicMock ()
343+ # this mimics a value coming from the front end
344+ widget ._handle_msg ({
345+ 'content' : {
346+ 'data' : {
347+ 'method' : 'update' ,
348+ 'state' : {
349+ 'value' : 42 ,
350+ }
351+ }
352+ }
353+ })
354+ assert widget .value == 42
355+
356+ # widget._send.assert_not_called(calls)
357+ widget ._send .assert_not_called ()
358+
359+ # a regular set should sync to the frontend
360+ widget .value = 43
361+ widget ._send .assert_has_calls ([mock .call ({'method' : 'update' , 'state' : {'value' : 43.0 }, 'buffer_paths' : [], 'echo' : ['value' ]}, buffers = [])])
0 commit comments