def test_dispose_inner_ui(self): obj = DummyObject() view = View( Group( Item( "number", editor=BasicEditorFactory(klass=EditorWithCustomWidget), ), ), ) ui = obj.edit_traits(view=view) editor, = ui.get_editors("number") gui = GUI() with ensure_destroyed(ui): # This represents an event handler that causes a nested UI to be # disposed. gui.invoke_later(editor.dispose_inner_ui) # Allowing GUI to process the disposal requests is crucial. # This requirement should be satisfied in production setting # where the dispose method is part of an event handler. # Not doing so before disposing the main UI would be a programming # error in tests settings. with reraise_exceptions(): process_cascade_events() gui.invoke_later(close_control, ui.control) with reraise_exceptions(): process_cascade_events() self.assertIsNone(ui.control)
def test_list_str_editor_multi_selection(self): view = get_view(multi_select=True) with reraise_exceptions(), self.setup_gui( ListStrModel(), view ) as editor: self.assertEqual(editor.multi_selected_indices, []) self.assertEqual(editor.multi_selected, []) set_selected_multiple(editor, [0, 1]) process_cascade_events() self.assertEqual(editor.multi_selected_indices, [0, 1]) self.assertEqual(editor.multi_selected, ["one", "two"]) set_selected_multiple(editor, [2]) process_cascade_events() self.assertEqual(editor.multi_selected_indices, [2]) self.assertEqual(editor.multi_selected, ["three"]) clear_selection(editor) process_cascade_events() self.assertEqual(editor.multi_selected_indices, []) self.assertEqual(editor.multi_selected, [])
def check_enum_mappings_name_change(self, style, mode): class IntEnumModel(HasTraits): value = Int() possible_values = List([0, 1]) formatted_view = View( UItem( 'value', editor=EnumEditor( name="object.possible_values", format_func=lambda v: str(bool(v)).upper(), mode=mode, ), style=style, ) ) model = IntEnumModel() with reraise_exceptions(), self.setup_ui( model, formatted_view ) as editor: self.assertEqual(editor.names, ["FALSE", "TRUE"]) self.assertEqual(editor.mapping, {"FALSE": 0, "TRUE": 1}) self.assertEqual(editor.inverse_mapping, {0: "FALSE", 1: "TRUE"}) model.possible_values = [1, 0] self.assertEqual(editor.names, ["TRUE", "FALSE"]) self.assertEqual(editor.mapping, {"TRUE": 1, "FALSE": 0}) self.assertEqual(editor.inverse_mapping, {1: "TRUE", 0: "FALSE"})
def test_init_dispose(self): # Test init and dispose of the editor. view = View(Item("true_or_false", editor=BooleanEditor())) obj = BoolModel() with reraise_exceptions(), \ create_ui(obj, dict(view=view)): pass
def test_tabular_editor_single_selection_changed(self): with reraise_exceptions(), \ self.report_and_editor(get_view()) as (report, editor): process_cascade_events() people = report.people self.assertEqual(get_selected_rows(editor), []) report.selected_row = 1 process_cascade_events() self.assertEqual(get_selected_rows(editor), [1]) self.assertEqual(report.selected, people[1]) report.selected = people[2] process_cascade_events() self.assertEqual(get_selected_rows(editor), [2]) self.assertEqual(report.selected_row, 2) # Selected set to invalid value doesn't change anything report.selected = Person(name="invalid", age=-1) process_cascade_events() self.assertEqual(get_selected_rows(editor), [2]) self.assertEqual(report.selected_row, 2) # -1 clears selection report.selected_row = -1 process_cascade_events() self.assertEqual(get_selected_rows(editor), []) self.assertEqual(report.selected, None)
def test_csv_editor_external_append(self): # Behavior: CSV editor is notified when an element is appended to the # list externally def _wx_get_text_value(ui): txt_ctrl = ui.control.FindWindowByName("text", ui.control) return txt_ctrl.GetValue() def _qt_get_text_value(ui): from pyface import qt txt_ctrl = ui.control.findChild(qt.QtGui.QLineEdit) return txt_ctrl.text() list_of_floats = ListOfFloats(data=[1.0]) csv_view = ListOfFloatsWithCSVEditor(model=list_of_floats) with reraise_exceptions(), create_ui(csv_view) as ui: # add element to list, make sure that editor knows about it list_of_floats.data.append(3.14) # get current value from editor if is_wx(): value_str = _wx_get_text_value(ui) elif is_qt(): value_str = _qt_get_text_value(ui) expected = csv_list_editor._format_list_str([1.0, 3.14]) self.assertEqual(value_str, expected)
def check_init_and_dispose(self, style): # Test init and dispose by opening and closing the UI view = View(Item("dir_path", editor=DirectoryEditor(), style=style)) obj = DirectoryModel() with reraise_exceptions(), \ create_ui(obj, dict(view=view)): pass
def _test_tree_node_object_releases_listeners(self, hide_root, nodes=None, trait="bogus_list", expected_listeners=1): """ The TreeEditor should release the listener to the root node's children when it's disposed of. """ bogus = BogusTreeNodeObject(bogus_list=[BogusTreeNodeObject()]) tree_editor_view = BogusTreeNodeObjectView(bogus=bogus, hide_root=hide_root, nodes=nodes) with reraise_exceptions(), \ create_ui(tree_editor_view) as ui: # The TreeEditor sets a listener on the bogus object's # children list notifiers_list = bogus.trait(trait)._notifiers(False) self.assertEqual(expected_listeners, len(notifiers_list)) if trait == "name": # fire a label change bogus.name = "Something else" else: # change the children bogus.bogus_list.append(BogusTreeNodeObject()) # The listener should be removed after the UI has been closed notifiers_list = bogus.trait(trait)._notifiers(False) self.assertEqual(0, len(notifiers_list))
def check_checklist_mappings_name_change(self, style): class ListModel(HasTraits): value = List() possible_values = List(["one", "two"]) check_list_editor_factory = CheckListEditor( name="object.possible_values", format_func=lambda v: v.upper(), ) formatted_view = View( UItem( "value", editor=check_list_editor_factory, style=style, )) model = ListModel() with reraise_exceptions(), \ self.setup_ui(model, formatted_view) as editor: self.assertEqual(editor.names, ["ONE", "TWO"]) model.possible_values = ["two", "one"] self.assertEqual(editor.names, ["TWO", "ONE"])
def test_wx_spin_control_editing_should_not_crash(self): # Bug: when editing the text part of a spin control box, pressing # the OK button raises an AttributeError on Mac OS X num = NumberWithSpinnerEditor() try: with reraise_exceptions(), create_ui(num) as ui: # the following is equivalent to clicking in the text control # of the range editor, enter a number, and clicking ok without # defocusing # SpinCtrl object spin = ui.control.FindWindowByName("wxSpinCtrl", ui.control) spin.SetFocusFromKbd() # on Windows, a wxSpinCtrl does not have children, and we # cannot do the more fine-grained testing below if len(spin.GetChildren()) == 0: spin.SetValue("4") else: # TextCtrl object of the spin control spintxt = spin.FindWindowByName("text", spin) spintxt.SetValue("4") except AttributeError: # if all went well, we should not be here self.fail("AttributeError raised")
def test_wx_spin_control_editing_does_not_update(self): # Bug: when editing the text part of a spin control box, pressing # the OK button does not update the value of the HasTraits class # on Mac OS X # But under wx >= 3.0 this has been resolved import wx if wx.VERSION >= (3, 0): return num = NumberWithSpinnerEditor() with reraise_exceptions(), create_ui(num) as ui: # the following is equivalent to clicking in the text control of # the range editor, enter a number, and clicking ok without # defocusing # SpinCtrl object spin = ui.control.FindWindowByName("wxSpinCtrl") spin.SetFocusFromKbd() # on Windows, a wxSpinCtrl does not have children, and we cannot do # the more fine-grained testing below if len(spin.GetChildren()) == 0: spin.SetValueString("4") else: # TextCtrl object of the spin control spintxt = spin.FindWindowByName("text") spintxt.SetValue("4") # if all went well, the number traits has been updated and its # value is 4 self.assertEqual(num.number, 4)
def test_selection_listener_disconnected(self): """Check that selection listeners get correctly disconnected""" from pyface.qt.QtGui import QApplication, QItemSelectionModel from pyface.ui.qt4.util.testing import event_loop obj = ListStrEditorWithSelectedIndex(values=["value1", "value2"]) with reraise_exceptions(): qt_app = QApplication.instance() if qt_app is None: qt_app = QApplication([]) # open the UI and run until the dialog is closed with create_ui(obj, dict(view=single_select_item_view)) as ui: pass # now run again and change the selection with create_ui( obj, dict(view=single_select_item_view) ) as ui, event_loop(): editor = ui.get_editors("values")[0] list_view = editor.list_view mi = editor.model.index(1) list_view.selectionModel().select( mi, QItemSelectionModel.SelectionFlag.ClearAndSelect ) obj.selected = "value2"
def test_list_str_editor_update_editor_multi_wx(self): # WX editor uses selected indices as the source of truth when updating model = ListStrModel() view = get_view(multi_select=True) with reraise_exceptions(), self.setup_gui(model, view) as editor: set_selected_multiple(editor, [0]) process_cascade_events() # Sanity check self.assertEqual(editor.multi_selected_indices, [0]) self.assertEqual(editor.multi_selected, ["one"]) model.value = ["two", "one"] process_cascade_events() # Selected_index remains 0 and selected is updated accordingly self.assertEqual(get_selected_indices(editor), [0]) self.assertEqual(editor.multi_selected_indices, [0]) self.assertEqual(editor.multi_selected, ["two"]) # Empty list creates a case of no longer valid selection model.value = [] process_cascade_events() # Internal view model selection is reset, but editor selection # values are not (see issue enthought/traitsui#872) self.assertEqual(get_selected_indices(editor), []) self.assertEqual(editor.multi_selected_indices, [0]) self.assertEqual(editor.multi_selected, ["two"])
def test_list_str_editor_update_editor_single_qt(self): # QT editor uses selected items as the source of truth when updating model = ListStrModel() with reraise_exceptions(), self.setup_gui(model, get_view()) as editor: set_selected_single(editor, 0) process_cascade_events() # Sanity check self.assertEqual(editor.selected_index, 0) self.assertEqual(editor.selected, "one") model.value = ["two", "one"] process_cascade_events() # Selected remains "one" and indices are updated accordingly self.assertEqual(get_selected_indices(editor), [1]) self.assertEqual(editor.selected_index, 1) self.assertEqual(editor.selected, "one") # Removing "one" creates a case of no longer valid selection model.value = ["two", "three"] process_cascade_events() # Internal view model selection is reset, but editor selection # values are not (see issue enthought/traitsui#872) self.assertEqual(get_selected_indices(editor), []) self.assertEqual(editor.selected_index, 1) self.assertEqual(editor.selected, "one")
def test_list_str_editor_multi_selection_items_changed(self): view = get_view(multi_select=True) with reraise_exceptions(), self.setup_gui( ListStrModel(), view ) as editor: self.assertEqual(get_selected_indices(editor), []) editor.multi_selected_indices.extend([0, 1]) process_cascade_events() self.assertEqual(get_selected_indices(editor), [0, 1]) self.assertEqual(editor.multi_selected, ["one", "two"]) editor.multi_selected_indices[1] = 2 process_cascade_events() self.assertEqual(get_selected_indices(editor), [0, 2]) self.assertEqual(editor.multi_selected, ["one", "three"]) editor.multi_selected[0] = "two" process_cascade_events() self.assertEqual(sorted(get_selected_indices(editor)), [1, 2]) self.assertEqual(sorted(editor.multi_selected_indices), [1, 2]) # If a change in multi_selected involves an invalid value, nothing # is changed editor.multi_selected[0] = "four" process_cascade_events() self.assertEqual(sorted(get_selected_indices(editor)), [1, 2]) self.assertEqual(sorted(editor.multi_selected_indices), [1, 2])
def test_simple_set_editor_deleted_valid_values(self): editor_factory = SetEditor(values=["one", "two", "three", "four"]) view = View(UItem( "value", editor=editor_factory, style="simple", )) list_edit = ListModel() with reraise_exceptions(), self.setup_gui(list_edit, view) as editor: self.assertEqual(get_list_items(editor._unused), ["four", "three"]) self.assertEqual(get_list_items(editor._used), ["one", "two"]) editor_factory.values = ["two", "three", "four"] process_cascade_events() self.assertEqual(get_list_items(editor._unused), ["four", "three"]) # FIXME issue enthought/traitsui#840 if is_wx(): with self.assertRaises(AssertionError): self.assertEqual(get_list_items(editor._used), ["two"]) self.assertEqual(get_list_items(editor._used), ["one", "two"]) else: self.assertEqual(get_list_items(editor._used), ["two"]) self.assertEqual(list_edit.value, ["two"])
def test_simple_set_editor_use_ordered_selected(self): # Initiate with non-alphabetical list model = ListModel(value=["two", "one"]) with reraise_exceptions(), self.setup_gui( model, get_view(ordered=True)) as editor: self.assertEqual(get_list_items(editor._unused), ["four", "three"]) # Used list maintains the order self.assertEqual(get_list_items(editor._used), ["two", "one"]) click_on_item(editor, 1, in_used=False) process_cascade_events() self.assertTrue(is_control_enabled(editor._use)) self.assertFalse(is_control_enabled(editor._unuse)) click_button(editor._use) process_cascade_events() self.assertEqual(get_list_items(editor._unused), ["four"]) # Button inserts at the top self.assertEqual(get_list_items(editor._used), ["three", "two", "one"]) self.assertEqual(editor._get_selected_strings(editor._used), ["three"])
def check_checklist_mappings_tuple_name_change(self, style): class ListModel(HasTraits): value = List() possible_values = List([(1, "one"), (2, "two")]) check_list_editor_factory = CheckListEditor( name="object.possible_values", format_func=lambda t: t[1].upper(), ) formatted_view = View( UItem( "value", editor=check_list_editor_factory, style=style, )) model = ListModel() with reraise_exceptions(), \ self.setup_ui(model, formatted_view) as editor: # FIXME issue enthought/traitsui#841 with self.assertRaises(AssertionError): self.assertEqual(editor.names, ["ONE", "TWO"]) self.assertEqual(editor.names, ["one", "two"]) model.possible_values = [(2, "two"), (1, "one")] # FIXME issue enthought/traitsui#841 with self.assertRaises(AssertionError): self.assertEqual(editor.names, ["TWO", "ONE"]) self.assertEqual(editor.names, ["two", "one"])
def test_data_frame_editor_multi_select(self): view = View( Item("data", editor=DataFrameEditor(multi_select=True), width=400)) viewer = sample_data() with reraise_exceptions(), \ create_ui(viewer, dict(view=view)): pass
def test_simple_check_list_editor_invalid_current_values(self): list_edit = ListModel(value=[1, "two", "a", object(), "one"]) with reraise_exceptions(), \ self.setup_gui(list_edit, get_view("simple")): self.assertEqual(list_edit.value, ["two", "one"])
def test_error_from_gui_captured_and_raise(self): def raise_error_1(): raise ZeroDivisionError() def raise_error_2(): raise IndexError() # without the context manager: # - with Qt5, the test run will be aborted prematurely. # - with Qt4, the traceback is printed and the test passes. # - with Wx, the traceback is printed and the test passes. # With the context manager, the exception is always reraised. gui = GUI() with self.assertRaises(RuntimeError) as exception_context, \ self.assertLogs("traitsui") as watcher: with reraise_exceptions(): gui.invoke_later(raise_error_1) gui.invoke_later(raise_error_2) gui.process_events() error_msg = str(exception_context.exception) self.assertIn("ZeroDivisionError", error_msg) self.assertIn("IndexError", error_msg) log_content1, log_content2 = watcher.output self.assertIn("ZeroDivisionError", log_content1) self.assertIn("IndexError", log_content2)
def test_simple_set_editor_cant_move_all_button_existence(self): view = get_view(can_move_all=False) with reraise_exceptions(), \ self.setup_gui(ListModel(), view) as editor: self.assertIsNone(editor._use_all) self.assertIsNone(editor._unuse_all)
def test_tabular_editor_multi_selection(self): view = get_view(multi_select=True) with reraise_exceptions(), \ self.report_and_editor(view) as (report, editor): process_cascade_events() people = report.people self.assertEqual(report.selected_rows, []) self.assertEqual(report.multi_selected, []) set_selected_multiple(editor, [0, 1]) process_cascade_events() self.assertEqual(report.selected_rows, [0, 1]) self.assertEqual(report.multi_selected, people[:2]) set_selected_multiple(editor, [2]) process_cascade_events() self.assertEqual(report.selected_rows, [2]) self.assertEqual(report.multi_selected, [people[2]]) clear_selection(editor) process_cascade_events() self.assertEqual(report.selected_rows, []) self.assertEqual(report.multi_selected, [])
def test_qt_tuple_editor(self): # Behavior: when editing the text of a tuple editor, # value get updated immediately. from pyface import qt val = TupleEditor() with reraise_exceptions(), create_ui(val) as ui: # the following is equivalent to clicking in the text control of # the range editor, enter a number, and clicking ok without # defocusing # text element inside the spin control lineedits = ui.control.findChildren(qt.QtGui.QLineEdit) lineedits[0].setFocus() lineedits[0].clear() lineedits[0].insert("4") lineedits[1].setFocus() lineedits[1].clear() lineedits[1].insert("6") lineedits[2].setFocus() lineedits[2].clear() lineedits[2].insert("fun") # if all went well, the tuple trait has been updated and its value # is (4, 6, "fun") self.assertEqual(val.tup, (4, 6, "fun"))
def test_tabular_editor_multi_selection_items_changed(self): view = get_view(multi_select=True) with reraise_exceptions(), \ self.report_and_editor(view) as (report, editor): process_cascade_events() people = report.people self.assertEqual(get_selected_rows(editor), []) report.selected_rows.extend([0, 1]) process_cascade_events() self.assertEqual(get_selected_rows(editor), [0, 1]) self.assertEqual(report.multi_selected, people[:2]) report.selected_rows[1] = 2 process_cascade_events() self.assertEqual(get_selected_rows(editor), [0, 2]) self.assertEqual(report.multi_selected, people[0:3:2]) report.multi_selected[0] = people[1] process_cascade_events() self.assertEqual(sorted(get_selected_rows(editor)), [1, 2]) self.assertEqual(sorted(report.selected_rows), [1, 2]) # If there's a single invalid value, nothing is updated report.multi_selected[0] = Person(name="invalid", age=-1) process_cascade_events() self.assertEqual(sorted(get_selected_rows(editor)), [1, 2]) self.assertEqual(sorted(report.selected_rows), [1, 2])
def test_datetime_editor_mutate_minimum_datetime_after_init(self): # Test if the minimum_datetime on the editor is mutated after init, # the values on the edited object and the view are synchronized. editor_factory = DatetimeEditor(minimum_datetime=datetime.datetime( 2000, 1, 1), ) view = get_date_time_simple_view(editor_factory) instance = InstanceWithDatetime() with reraise_exceptions(), \ self.launch_editor(instance, view) as editor: # This value is in-range instance.date_time = datetime.datetime(2001, 1, 1) # Now change the editor's minimum datetime such that the original # value is out-of-range new_minimum_datetime = datetime.datetime(2010, 1, 1) editor.minimum_datetime = new_minimum_datetime # does not seem needed to flush the event loop, but just in case. process_cascade_events() displayed_value = to_datetime(editor.control.dateTime()) self.assertEqual(instance.date_time, displayed_value) self.assertEqual(displayed_value, new_minimum_datetime)
def test_show_right_with_no_label(self): # Bug: If one set show_left=False, show_label=False on a non-resizable # item like a checkbox, the Qt backend tried to set the label's size # policy and failed because label=None. with reraise_exceptions(), \ create_ui(NoLabelResizeTestDialog()): pass
def test_simple_editor_mapping_values(self): class IntListModel(HasTraits): value = List() set_editor_factory = SetEditor( values=[0, 1], format_func=lambda v: str(bool(v)).upper()) formatted_view = View( UItem( "value", editor=set_editor_factory, style="simple", )) with reraise_exceptions(), self.setup_ui(IntListModel(), formatted_view) as editor: self.assertEqual(editor.names, ["FALSE", "TRUE"]) self.assertEqual(editor.mapping, {"FALSE": 0, "TRUE": 1}) self.assertEqual(editor.inverse_mapping, {0: "FALSE", 1: "TRUE"}) set_editor_factory.values = [1, 0] self.assertEqual(editor.names, ["TRUE", "FALSE"]) self.assertEqual(editor.mapping, {"TRUE": 1, "FALSE": 0}) self.assertEqual(editor.inverse_mapping, {1: "TRUE", 0: "FALSE"})
def check_enum_mappings_value_change(self, style, mode): class IntEnumModel(HasTraits): value = Int() enum_editor_factory = EnumEditor( values=[0, 1], format_func=lambda v: str(bool(v)).upper(), mode=mode, ) formatted_view = View( UItem( "value", editor=enum_editor_factory, style=style, ) ) with reraise_exceptions(), self.setup_ui( IntEnumModel(), formatted_view ) as editor: self.assertEqual(editor.names, ["FALSE", "TRUE"]) self.assertEqual(editor.mapping, {"FALSE": 0, "TRUE": 1}) self.assertEqual(editor.inverse_mapping, {0: "FALSE", 1: "TRUE"}) enum_editor_factory.values = [1, 0] self.assertEqual(editor.names, ["TRUE", "FALSE"]) self.assertEqual(editor.mapping, {"TRUE": 1, "FALSE": 0}) self.assertEqual(editor.inverse_mapping, {1: "TRUE", 0: "FALSE"})
def test_simple_editor_mapping_name(self): class IntListModel(HasTraits): value = List() possible_values = List([0, 1]) formatted_view = View( UItem( 'value', editor=SetEditor( name="object.possible_values", format_func=lambda v: str(bool(v)).upper(), ), style="simple", )) model = IntListModel() with reraise_exceptions(), self.setup_ui(model, formatted_view) as editor: self.assertEqual(editor.names, ["FALSE", "TRUE"]) self.assertEqual(editor.mapping, {"FALSE": 0, "TRUE": 1}) self.assertEqual(editor.inverse_mapping, {0: "FALSE", 1: "TRUE"}) model.possible_values = [1, 0] self.assertEqual(editor.names, ["TRUE", "FALSE"]) self.assertEqual(editor.mapping, {"TRUE": 1, "FALSE": 0}) self.assertEqual(editor.inverse_mapping, {1: "TRUE", 0: "FALSE"})