1#  Copyright (c) 2008-19, Enthought, Inc.
2#  All rights reserved.
3#
4#  This software is provided without warranty under the terms of the BSD
5#  license included in LICENSE.txt and may be redistributed only
6#  under the conditions described in the aforementioned license.  The license
7#  is also available online at http://www.enthought.com/licenses/BSD.txt
8#
9#  Thanks for using Enthought open source!
10#
11#  Author: Corran Webster
12#  Date:   August 12, 2019
13
14import unittest
15
16from traits.api import Any, Bool, Event, Float, HasTraits, Int, List, Undefined
17from traits.trait_base import xgetattr
18
19from traitsui.context_value import ContextValue, CVFloat, CVInt
20from traitsui.editor import Editor
21from traitsui.editor_factory import EditorFactory
22from traitsui.handler import default_handler
23from traitsui.ui import UI
24from traitsui.tests._tools import (
25    BaseTestMixin, GuiTestAssistant, no_gui_test_assistant
26)
27
28
29class FakeControl(HasTraits):
30    """ A pure Traits object that fakes being a control.
31
32    It can aither hold a value (mimicking a field), or have an event
33    which is listened to (mimicking a button or similar control).
34    """
35
36    #: The value stored in the control.
37    control_value = Any()
38
39    #: An event which also can be fired.
40    control_event = Event()
41
42
43class StubEditorFactory(EditorFactory):
44    """ A minimal editor factory
45
46    This simply holds state that may or may not be copied to the
47    editor.  No attempt is made to handle custom/readonly/etc.
48    variation.
49    """
50
51    #: Whether or not the traits are events.
52    is_event = Bool()
53
54    #: Whether or not the traits are events.
55    auxiliary_value = Any(sync_value=True)
56
57    #: Whether or not the traits are events.
58    auxiliary_cv_int = CVInt
59
60    #: Whether or not the traits are events.
61    auxiliary_cv_float = CVFloat
62
63    def _auxiliary_cv_int_default(self):
64        return 0
65
66    def _auxiliary_cv_float_default(self):
67        return 0.0
68
69
70class StubEditor(Editor):
71    """ A minimal editor implementaton for a StubEditorFactory.
72
73    The editor creates a FakeControl instance as its control object
74    and keeps values synchronized either to `control_value` or
75    `control_event` (if `is_event` is True).
76    """
77
78    #: Whether or not the traits are events.
79    is_event = Bool()
80
81    #: An auxiliary value we want to synchronize.
82    auxiliary_value = Any()
83
84    #: An auxiliary list we want to synchronize.
85    auxiliary_list = List()
86
87    #: An auxiliary event we want to synchronize.
88    auxiliary_event = Event()
89
90    #: An auxiliary int we want to synchronize with a context value.
91    auxiliary_cv_int = Int(sync_value="from")
92
93    #: An auxiliary float we want to synchronize with a context value.
94    auxiliary_cv_float = Float()
95
96    def init(self, parent):
97        self.control = FakeControl()
98        self.is_event = self.factory.is_event
99        if self.is_event:
100            self.control.on_trait_change(self.update_object, "control_event")
101        else:
102            self.control.on_trait_change(self.update_object, "control_value")
103
104    def dispose(self):
105        if self.is_event:
106            self.control.on_trait_change(
107                self.update_object, "control_event", remove=True
108            )
109        else:
110            self.control.on_trait_change(
111                self.update_object, "control_value", remove=True
112            )
113        super(StubEditor, self).dispose()
114
115    def update_editor(self):
116        if self.is_event:
117            self.control.control_event = True
118        else:
119            self.control.control_value = self.value
120
121    def update_object(self, new):
122        if self.control is not None:
123            if not self._no_update:
124                self._no_update = True
125                try:
126                    self.value = new
127                finally:
128                    self._no_update = False
129
130    def set_focus(self, parent):
131        pass
132
133
134class UserObject(HasTraits):
135    """ A simple HasTraits class with a variety of state. """
136
137    #: The value being edited.
138    user_value = Any("test")
139
140    #: An auxiliary user value
141    user_auxiliary = Any(10)
142
143    #: An list user value
144    user_list = List(["one", "two", "three"])
145
146    #: An event user value
147    user_event = Event()
148
149    #: A state that is to be synchronized with the editor.
150    invalid_state = Bool()
151
152
153def create_editor(
154        context=None,
155        object_name="object",
156        name="user_value",
157        factory=None,
158        is_event=False,
159):
160    if context is None:
161        user_object = UserObject()
162        context = {"object": user_object}
163    elif "." in object_name:
164        context_name, xname = object_name.split(".", 1)
165        context_object = context[context_name]
166        user_object = xgetattr(context_object, xname)
167    else:
168        user_object = context[object_name]
169    ui = UI(context=context, handler=default_handler())
170
171    if factory is None:
172        factory = StubEditorFactory()
173    factory.is_event = is_event
174
175    editor = StubEditor(
176        parent=None,
177        ui=ui,
178        object_name=object_name,
179        name=name,
180        factory=factory,
181        object=user_object,
182    )
183    return editor
184
185
186@unittest.skipIf(no_gui_test_assistant, "No GuiTestAssistant")
187class TestEditor(BaseTestMixin, GuiTestAssistant, unittest.TestCase):
188
189    def setUp(self):
190        BaseTestMixin.setUp(self)
191        GuiTestAssistant.setUp(self)
192
193    def tearDown(self):
194        GuiTestAssistant.tearDown(self)
195        BaseTestMixin.tearDown(self)
196
197    def change_user_value(self, editor, object, name, value):
198        if editor.is_event:
199            control_name = "control_event"
200        else:
201            control_name = "control_value"
202
203        # test the value in the control changes
204        with self.assertTraitChanges(editor.control, control_name, count=1):
205            self.gui.set_trait_later(object, name, value)
206            self.event_loop_helper.event_loop_with_timeout(repeat=6)
207
208    def change_control_value(self, editor, object, name, value):
209        if editor.is_event:
210            control_name = "control_event"
211        else:
212            control_name = "control_value"
213
214        # test the value in the user object changes
215        with self.assertTraitChanges(object, name, count=1):
216            self.gui.set_trait_later(editor.control, control_name, value)
217            self.event_loop_helper.event_loop_with_timeout(repeat=6)
218
219    def test_lifecycle(self):
220        editor = create_editor()
221
222        self.assertEqual(editor.old_value, "test")
223        self.assertEqual(editor.name, "user_value")
224        self.assertEqual(editor.extended_name, "user_value")
225        self.assertEqual(editor.value, "test")
226        self.assertEqual(editor.str_value, "test")
227        self.assertIs(editor.value_trait, editor.object.trait("user_value"))
228        self.assertIs(editor.context_object, editor.ui.context["object"])
229
230        editor.prepare(None)
231
232        # preparation creates the control and sets the control value
233        self.assertEqual(editor.value, "test")
234        self.assertEqual(editor.control.control_value, "test")
235
236        with self.assertTraitChanges(editor.ui, "modified", count=1):
237            self.change_user_value(
238                editor, editor.object, "user_value", "new test"
239            )
240
241        self.assertEqual(editor.value, "new test")
242        self.assertEqual(editor.control.control_value, "new test")
243        self.assertTrue(editor.ui.modified)
244
245        self.change_control_value(
246            editor, editor.object, "user_value", "even newer test"
247        )
248
249        self.assertEqual(editor.value, "even newer test")
250        self.assertEqual(editor.object.user_value, "even newer test")
251
252        editor.dispose()
253
254        self.assertIsNone(editor.object)
255        self.assertIsNone(editor.factory)
256        self.assertIsNone(editor.control)
257
258    def test_context_object(self):
259        user_object = UserObject(user_value="other_test")
260        context = {"object": UserObject(), "other_object": user_object}
261        editor = create_editor(
262            context=context, object_name="other_object"
263        )
264
265        self.assertEqual(editor.old_value, "other_test")
266        self.assertEqual(editor.name, "user_value")
267        self.assertEqual(editor.extended_name, "user_value")
268        self.assertIs(editor.context_object, editor.ui.context["other_object"])
269
270        editor.prepare(None)
271
272        # preparation creates the control and sets the control value
273        self.assertEqual(editor.value, "other_test")
274        self.assertEqual(editor.control.control_value, "other_test")
275
276        self.change_user_value(editor, user_object, "user_value", "new test")
277
278        self.assertEqual(editor.value, "new test")
279        self.assertEqual(editor.control.control_value, "new test")
280
281        self.change_control_value(
282            editor, user_object, "user_value", "even newer test"
283        )
284
285        self.assertEqual(editor.value, "even newer test")
286        self.assertEqual(editor.object.user_value, "even newer test")
287
288        editor.dispose()
289
290    def test_event_trait(self):
291        editor = create_editor(name="user_event", is_event=True)
292        user_object = editor.object
293
294        self.assertEqual(editor.name, "user_event")
295        self.assertEqual(editor.extended_name, "user_event")
296        self.assertIs(editor.context_object, editor.ui.context["object"])
297
298        editor.prepare(None)
299
300        # preparation creates the control and sets the control value
301        self.assertIs(editor.value, Undefined)
302
303        self.change_user_value(editor, user_object, "user_event", True)
304        self.change_control_value(editor, user_object, "user_event", True)
305
306        editor.dispose()
307
308    def test_chained_object(self):
309        context = {
310            "object": UserObject(
311                user_auxiliary=UserObject(user_value="other_test")
312            )
313        }
314        user_object = context["object"].user_auxiliary
315        editor = create_editor(
316            context=context, object_name="object.user_auxiliary"
317        )
318
319        self.assertEqual(editor.old_value, "other_test")
320        self.assertEqual(editor.name, "user_value")
321        self.assertEqual(editor.extended_name, "user_auxiliary.user_value")
322        self.assertIs(editor.context_object, editor.ui.context["object"])
323
324        editor.prepare(None)
325
326        # preparation creates the control and sets the control value
327        self.assertEqual(editor.value, "other_test")
328        self.assertEqual(editor.control.control_value, "other_test")
329
330        self.change_user_value(editor, user_object, "user_value", "new test")
331
332        self.assertEqual(editor.value, "new test")
333        self.assertEqual(editor.control.control_value, "new test")
334
335        self.change_control_value(
336            editor, user_object, "user_value", "even newer test"
337        )
338
339        self.assertEqual(editor.value, "even newer test")
340        self.assertEqual(editor.object.user_value, "even newer test")
341
342        # test changing the chained object
343        new_user_object = UserObject(user_value="new object")
344        with self.assertTraitChanges(editor, "object", count=1):
345            self.change_user_value(
346                editor, context["object"], "user_auxiliary", new_user_object
347            )
348
349        self.assertEqual(editor.value, "new object")
350        self.assertIs(editor.object, new_user_object)
351        self.assertEqual(editor.object.user_value, "new object")
352
353        editor.dispose()
354
355    def test_factory_sync_simple(self):
356        factory = StubEditorFactory(auxiliary_value="test")
357        editor = create_editor(factory=factory)
358        editor.prepare(None)
359
360        # preparation copies the auxiliary value from the factory
361        self.assertIs(editor.auxiliary_value, "test")
362
363        editor.dispose()
364
365    def test_factory_sync_cv_simple(self):
366        factory = StubEditorFactory()
367        editor = create_editor(factory=factory)
368        editor.prepare(None)
369
370        # preparation copies the auxiliary CV int value from the factory
371        self.assertIs(editor.auxiliary_cv_int, 0)
372
373        editor.dispose()
374
375    def test_parse_extended_name(self):
376        context = {
377            "object": UserObject(
378                user_auxiliary=UserObject(user_value="other_test")
379            ),
380            "other_object": UserObject(user_value="another_test"),
381        }
382        editor = create_editor(context=context)
383        editor.prepare(None)
384
385        # test simple name
386        object, name, getter = editor.parse_extended_name("user_value")
387        value = getter()
388
389        self.assertIs(object, context["object"])
390        self.assertEqual(name, "user_value")
391        self.assertEqual(value, "test")
392
393        # test different context object name
394        object, name, getter = editor.parse_extended_name(
395            "other_object.user_value"
396        )
397        value = getter()
398
399        self.assertIs(object, context["other_object"])
400        self.assertEqual(name, "user_value")
401        self.assertEqual(value, "another_test")
402
403        # test chained name
404        object, name, getter = editor.parse_extended_name(
405            "object.user_auxiliary.user_value"
406        )
407        value = getter()
408
409        self.assertIs(object, context["object"])
410        self.assertEqual(name, "user_auxiliary.user_value")
411        self.assertEqual(value, "other_test")
412
413        editor.dispose()
414
415    # Test synchronizing built-in trait values between factory
416    # and editor.
417
418    def test_factory_sync_invalid_state(self):
419        # Test when object's trait that sets the invalid state changes,
420        # the invalid state on the editor changes
421        factory = StubEditorFactory(invalid="invalid_state")
422        user_object = UserObject(invalid_state=False)
423        context = {
424            "object": user_object,
425        }
426        editor = create_editor(context=context, factory=factory)
427        editor.prepare(None)
428        self.addCleanup(editor.dispose)
429
430        with self.assertTraitChanges(editor, "invalid", count=1):
431            user_object.invalid_state = True
432
433        self.assertTrue(editor.invalid)
434
435        with self.assertTraitChanges(editor, "invalid", count=1):
436            user_object.invalid_state = False
437
438        self.assertFalse(editor.invalid)
439
440    # Testing sync_value "from" ---------------------------------------------
441
442    def test_sync_value_from(self):
443        editor = create_editor()
444        user_object = editor.object
445        editor.prepare(None)
446
447        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
448            editor.sync_value(
449                "object.user_auxiliary", "auxiliary_value", "from"
450            )
451
452        self.assertEqual(editor.auxiliary_value, 10)
453
454        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
455            user_object.user_auxiliary = 11
456
457        self.assertEqual(editor.auxiliary_value, 11)
458
459        editor.dispose()
460
461        with self.assertTraitDoesNotChange(editor, "auxiliary_value"):
462            user_object.user_auxiliary = 12
463
464    def test_sync_value_from_object(self):
465        editor = create_editor()
466        user_object = editor.object
467        editor.prepare(None)
468
469        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
470            editor.sync_value("user_auxiliary", "auxiliary_value", "from")
471
472        self.assertEqual(editor.auxiliary_value, 10)
473
474        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
475            user_object.user_auxiliary = 11
476
477        self.assertEqual(editor.auxiliary_value, 11)
478
479        editor.dispose()
480
481        with self.assertTraitDoesNotChange(editor, "auxiliary_value"):
482            user_object.user_auxiliary = 12
483
484    def test_sync_value_from_context(self):
485        # set up the editor
486        user_object = UserObject()
487        other_object = UserObject(user_auxiliary=20)
488        context = {"object": user_object, "other_object": other_object}
489        editor = create_editor(context=context)
490        editor.prepare(None)
491
492        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
493            editor.sync_value(
494                "other_object.user_auxiliary", "auxiliary_value", "from"
495            )
496
497        self.assertEqual(editor.auxiliary_value, 20)
498
499        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
500            other_object.user_auxiliary = 11
501
502        self.assertEqual(editor.auxiliary_value, 11)
503
504        editor.dispose()
505
506        with self.assertTraitDoesNotChange(editor, "auxiliary_value"):
507            other_object.user_auxiliary = 12
508
509    def test_sync_value_from_chained(self):
510        # set up the editor
511        user_object = UserObject(user_auxiliary=UserObject(user_value=20))
512        context = {"object": user_object}
513        editor = create_editor(context=context)
514        editor.prepare(None)
515
516        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
517            editor.sync_value(
518                "object.user_auxiliary.user_value", "auxiliary_value", "from"
519            )
520
521        self.assertEqual(editor.auxiliary_value, 20)
522
523        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
524            user_object.user_auxiliary.user_value = 11
525
526        self.assertEqual(editor.auxiliary_value, 11)
527
528        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
529            user_object.user_auxiliary = UserObject(user_value=12)
530
531        self.assertEqual(editor.auxiliary_value, 12)
532
533        editor.dispose()
534
535        with self.assertTraitDoesNotChange(editor, "auxiliary_value"):
536            user_object.user_auxiliary.user_value = 13
537
538    def test_sync_value_from_list(self):
539        editor = create_editor()
540        user_object = editor.object
541        editor.prepare(None)
542
543        with self.assertTraitChanges(editor, "auxiliary_list", count=1):
544            editor.sync_value(
545                "object.user_list", "auxiliary_list", "from", is_list=True
546            )
547
548        self.assertEqual(editor.auxiliary_list, ["one", "two", "three"])
549
550        with self.assertTraitChanges(editor, "auxiliary_list", count=1):
551            user_object.user_list = ["one", "two"]
552
553        self.assertEqual(editor.auxiliary_list, ["one", "two"])
554
555        with self.assertTraitChanges(editor, "auxiliary_list_items", count=1):
556            user_object.user_list[1:] = ["four", "five"]
557
558        self.assertEqual(editor.auxiliary_list, ["one", "four", "five"])
559
560        editor.dispose()
561
562        with self.assertTraitDoesNotChange(editor, "auxiliary_list"):
563            user_object.user_list = ["one", "two", "three"]
564
565    def test_sync_value_from_event(self):
566        editor = create_editor()
567        user_object = editor.object
568        editor.prepare(None)
569
570        with self.assertTraitDoesNotChange(editor, "auxiliary_event"):
571            editor.sync_value(
572                "object.user_event", "auxiliary_event", "from", is_event=True
573            )
574
575        with self.assertTraitChanges(editor, "auxiliary_event", count=1):
576            user_object.user_event = True
577
578        editor.dispose()
579
580        with self.assertTraitDoesNotChange(editor, "auxiliary_event"):
581            user_object.user_event = True
582
583    def test_sync_value_from_cv(self):
584        factory = StubEditorFactory(
585            auxiliary_cv_int=ContextValue("object.user_auxiliary")
586        )
587        editor = create_editor(factory=factory)
588        user_object = editor.object
589
590        with self.assertTraitChanges(editor, "auxiliary_cv_int", count=1):
591            editor.prepare(None)
592
593        self.assertEqual(editor.auxiliary_cv_int, 10)
594
595        with self.assertTraitChanges(editor, "auxiliary_cv_int", count=1):
596            user_object.user_auxiliary = 11
597
598        self.assertEqual(editor.auxiliary_cv_int, 11)
599
600        editor.dispose()
601
602        with self.assertTraitDoesNotChange(editor, "auxiliary_cv_int"):
603            user_object.user_auxiliary = 12
604
605    # Testing sync_value "to" -----------------------------------------------
606
607    def test_sync_value_to(self):
608        editor = create_editor()
609        user_object = editor.object
610        editor.prepare(None)
611        editor.auxiliary_value = 20
612
613        with self.assertTraitChanges(user_object, "user_auxiliary", count=1):
614            editor.sync_value("object.user_auxiliary", "auxiliary_value", "to")
615
616        self.assertEqual(user_object.user_auxiliary, 20)
617
618        with self.assertTraitChanges(user_object, "user_auxiliary", count=1):
619            editor.auxiliary_value = 11
620
621        self.assertEqual(user_object.user_auxiliary, 11)
622
623        editor.dispose()
624
625        with self.assertTraitDoesNotChange(user_object, "user_auxiliary"):
626            editor.auxiliary_value = 12
627
628    def test_sync_value_to_object(self):
629        editor = create_editor()
630        user_object = editor.object
631        editor.prepare(None)
632        editor.auxiliary_value = 20
633
634        with self.assertTraitChanges(user_object, "user_auxiliary", count=1):
635            editor.sync_value("user_auxiliary", "auxiliary_value", "to")
636
637        self.assertEqual(user_object.user_auxiliary, 20)
638
639        with self.assertTraitChanges(user_object, "user_auxiliary", count=1):
640            editor.auxiliary_value = 11
641
642        self.assertEqual(user_object.user_auxiliary, 11)
643
644        editor.dispose()
645
646        with self.assertTraitDoesNotChange(user_object, "user_auxiliary"):
647            editor.auxiliary_value = 12
648
649    def test_sync_value_to_context(self):
650        # set up the editor
651        user_object = UserObject()
652        other_object = UserObject()
653        context = {"object": user_object, "other_object": other_object}
654        editor = create_editor(context=context)
655        editor.prepare(None)
656        editor.auxiliary_value = 20
657
658        with self.assertTraitChanges(other_object, "user_auxiliary", count=1):
659            editor.sync_value(
660                "other_object.user_auxiliary", "auxiliary_value", "to"
661            )
662
663        self.assertEqual(other_object.user_auxiliary, 20)
664
665        with self.assertTraitChanges(other_object, "user_auxiliary", count=1):
666            editor.auxiliary_value = 11
667
668        self.assertEqual(other_object.user_auxiliary, 11)
669
670        editor.dispose()
671
672        with self.assertTraitDoesNotChange(other_object, "user_auxiliary"):
673            editor.auxiliary_value = 12
674
675    def test_sync_value_to_chained(self):
676        user_object = UserObject(user_auxiliary=UserObject())
677        context = {"object": user_object}
678        editor = create_editor(context=context)
679        editor.prepare(None)
680        editor.auxiliary_value = 20
681
682        with self.assertTraitChanges(
683                user_object.user_auxiliary, "user_value", count=1
684        ):
685            editor.sync_value(
686                "object.user_auxiliary.user_value", "auxiliary_value", "to"
687            )
688
689        self.assertEqual(user_object.user_auxiliary.user_value, 20)
690
691        with self.assertTraitChanges(
692                user_object.user_auxiliary, "user_value", count=1
693        ):
694            editor.auxiliary_value = 11
695
696        self.assertEqual(user_object.user_auxiliary.user_value, 11)
697
698        editor.dispose()
699
700        with self.assertTraitDoesNotChange(
701                user_object.user_auxiliary, "user_value"
702        ):
703            editor.auxiliary_value = 12
704
705    def test_sync_value_to_list(self):
706        editor = create_editor()
707        user_object = editor.object
708        editor.prepare(None)
709
710        with self.assertTraitChanges(user_object, "user_list", count=1):
711            editor.sync_value(
712                "object.user_list", "auxiliary_list", "to", is_list=True
713            )
714
715        self.assertEqual(user_object.user_list, [])
716
717        with self.assertTraitChanges(user_object, "user_list", count=1):
718            editor.auxiliary_list = ["one", "two"]
719
720        self.assertEqual(user_object.user_list, ["one", "two"])
721
722        with self.assertTraitChanges(user_object, "user_list_items", count=1):
723            editor.auxiliary_list[1:] = ["four", "five"]
724
725        self.assertEqual(user_object.user_list, ["one", "four", "five"])
726
727        editor.dispose()
728
729        with self.assertTraitDoesNotChange(user_object, "user_list"):
730            editor.auxiliary_list = ["one", "two", "three"]
731
732    def test_sync_value_to_event(self):
733        editor = create_editor()
734        user_object = editor.object
735        editor.prepare(None)
736
737        with self.assertTraitDoesNotChange(user_object, "user_event"):
738            editor.sync_value(
739                "object.user_event", "auxiliary_event", "to", is_event=True
740            )
741
742        with self.assertTraitChanges(user_object, "user_event", count=1):
743            editor.auxiliary_event = True
744
745        editor.dispose()
746
747        with self.assertTraitDoesNotChange(user_object, "user_event"):
748            editor.auxiliary_event = True
749
750    def test_sync_value_to_cv(self):
751        factory = StubEditorFactory(
752            auxiliary_cv_float=ContextValue("object.user_auxiliary")
753        )
754        editor = create_editor(factory=factory)
755        user_object = editor.object
756        editor.auxiliary_cv_float = 20.0
757
758        with self.assertTraitChanges(user_object, "user_auxiliary", count=1):
759            editor.prepare(None)
760
761        self.assertEqual(user_object.user_auxiliary, 20)
762
763        with self.assertTraitChanges(user_object, "user_auxiliary", count=1):
764            editor.auxiliary_cv_float = 11.0
765
766        self.assertEqual(user_object.user_auxiliary, 11)
767
768        editor.dispose()
769
770        with self.assertTraitDoesNotChange(user_object, "user_auxiliary"):
771            editor.auxiliary_cv_float = 12.0
772
773    # Testing sync_value "both" -----------------------------------------------
774
775    def test_sync_value_both(self):
776        editor = create_editor()
777        user_object = editor.object
778        editor.prepare(None)
779
780        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
781            editor.sync_value(
782                "object.user_auxiliary", "auxiliary_value", "both"
783            )
784
785        self.assertEqual(editor.auxiliary_value, 10)
786
787        with self.assertTraitChanges(editor, "auxiliary_value", count=1):
788            user_object.user_auxiliary = 11
789
790        self.assertEqual(editor.auxiliary_value, 11)
791
792        with self.assertTraitChanges(user_object, "user_auxiliary", count=1):
793            editor.auxiliary_value = 12
794
795        self.assertEqual(user_object.user_auxiliary, 12)
796
797        editor.dispose()
798
799        with self.assertTraitDoesNotChange(editor, "auxiliary_value"):
800            user_object.user_auxiliary = 13
801
802        with self.assertTraitDoesNotChange(user_object, "user_auxiliary"):
803            editor.auxiliary_value = 14
804