@@ -80,7 +80,7 @@ def test_set_state_simple():
80
80
c = [False , True , False ],
81
81
))
82
82
83
- assert w .comm .messages == []
83
+ assert len ( w .comm .messages ) == 1
84
84
85
85
86
86
def test_set_state_transformer ():
@@ -94,7 +94,8 @@ def test_set_state_transformer():
94
94
data = dict (
95
95
buffer_paths = [],
96
96
method = 'update' ,
97
- state = dict (d = [False , True , False ])
97
+ state = dict (d = [False , True , False ]),
98
+ echo = ['d' ],
98
99
)))]
99
100
100
101
@@ -105,7 +106,7 @@ def test_set_state_data():
105
106
a = True ,
106
107
d = {'data' : data },
107
108
))
108
- assert w .comm .messages == []
109
+ assert len ( w .comm .messages ) == 1
109
110
110
111
111
112
def test_set_state_data_truncate ():
@@ -122,9 +123,10 @@ def test_set_state_data_truncate():
122
123
buffers = msg [1 ].pop ('buffers' )
123
124
assert msg == ((), dict (
124
125
data = dict (
125
- buffer_paths = [['d' , 'data' ]],
126
126
method = 'update' ,
127
- state = dict (d = {})
127
+ state = dict (d = {}, a = True ),
128
+ buffer_paths = [['d' , 'data' ]],
129
+ echo = ['a' , 'd' ],
128
130
)))
129
131
130
132
# Sanity:
@@ -144,8 +146,8 @@ def test_set_state_numbers_int():
144
146
i = 3 ,
145
147
ci = 4 ,
146
148
))
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
149
151
150
152
151
153
def test_set_state_numbers_float ():
@@ -156,8 +158,8 @@ def test_set_state_numbers_float():
156
158
cf = 2.0 ,
157
159
ci = 4.0
158
160
))
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
161
163
162
164
163
165
def test_set_state_float_to_float ():
@@ -167,8 +169,8 @@ def test_set_state_float_to_float():
167
169
f = 1.2 ,
168
170
cf = 2.6 ,
169
171
))
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
172
174
173
175
174
176
def test_set_state_cint_to_float ():
@@ -235,6 +237,7 @@ def _propagate_value(self, change):
235
237
# this mimics a value coming from the front end
236
238
widget .set_state ({'value' : 42 })
237
239
assert widget .value == 42
240
+ assert widget .stop is True
238
241
239
242
# we expect no new state to be sent
240
243
calls = []
@@ -263,8 +266,96 @@ def _propagate_value(self, change):
263
266
assert widget .other == 11
264
267
265
268
# 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' ] }
267
270
call42 = mock .call (msg , buffers = [])
268
271
269
272
calls = [call42 ]
270
273
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