diff --git a/traitsui/examples/demo/Standard_Editors/tests/test_TableEditor_demo.py b/traitsui/examples/demo/Standard_Editors/tests/test_TableEditor_demo.py new file mode 100644 index 000000000..352cd3296 --- /dev/null +++ b/traitsui/examples/demo/Standard_Editors/tests/test_TableEditor_demo.py @@ -0,0 +1,49 @@ +""" +This example demonstrates how to test interacting with a TableEditor. + +The GUI being tested is written in the demo under the same name (minus the +preceding 'test') in the outer directory. +""" + +import os +import runpy +import unittest + + +from traitsui.testing.api import ( + Cell, KeyClick, KeySequence, MouseClick, UITester +) + +#: Filename of the demo script +FILENAME = "TableEditor_demo.py" + +#: Path of the demo script +DEMO_PATH = os.path.join(os.path.dirname(__file__), "..", FILENAME) + + +class TestTableEditorDemo(unittest.TestCase): + + def test_list_editor_demo(self): + demo = runpy.run_path(DEMO_PATH)["demo"] + + tester = UITester() + with tester.create_ui(demo) as ui: + employees_table = tester.find_by_name(ui, "employees") + + # clicking a cell enters edit mode and selcts full text + cell_21 = employees_table.locate(Cell(2,1)) + cell_21.perform(MouseClick()) + cell_21.perform(KeySequence("Jones")) + cell_21.perform(KeyClick("Enter")) + + self.assertEqual(demo.employees[0].last_name, 'Jones') + + # third column corresponds to Full Name property + cell_32 = employees_table.locate(Cell(3,2)) + cell_32.perform(MouseClick()) + + +# Run the test(s) +unittest.TextTestRunner().run( + unittest.TestLoader().loadTestsFromTestCase(TestTableEditorDemo) +) diff --git a/traitsui/testing/api.py b/traitsui/testing/api.py index 469a9f8cc..5f016b88d 100644 --- a/traitsui/testing/api.py +++ b/traitsui/testing/api.py @@ -59,6 +59,7 @@ from .tester.command import ( MouseClick, + MouseDClick, KeyClick, KeySequence ) @@ -71,6 +72,7 @@ ) from .tester.locator import ( + Cell, Index, TargetById, TargetByName, @@ -84,6 +86,7 @@ DisplayedText, IsChecked, IsEnabled, + Selected, SelectedText ) diff --git a/traitsui/testing/tester/_ui_tester_registry/qt4/_interaction_helpers.py b/traitsui/testing/tester/_ui_tester_registry/qt4/_interaction_helpers.py index a048dcf2a..e97de8e1a 100644 --- a/traitsui/testing/tester/_ui_tester_registry/qt4/_interaction_helpers.py +++ b/traitsui/testing/tester/_ui_tester_registry/qt4/_interaction_helpers.py @@ -204,6 +204,127 @@ def mouse_click_item_view(model, view, index, delay): ) +def mouse_dclick_item_view(model, view, index, delay): + """ Perform mouse double click on the given QAbstractItemModel (model) and + QAbstractItemView (view) with the given row and column. + + Parameters + ---------- + model : QAbstractItemModel + Model from which QModelIndex will be obtained + view : QAbstractItemView + View from which the widget identified by the index will be + found and key sequence be performed. + index : QModelIndex + + Raises + ------ + LookupError + If the index cannot be located. + Note that the index error provides more + """ + check_q_model_index_valid(index) + rect = view.visualRect(index) + QTest.mouseDClick( + view.viewport(), + QtCore.Qt.LeftButton, + QtCore.Qt.NoModifier, + rect.center(), + delay=delay, + ) + + +def key_sequence_item_view(model, view, index, sequence, delay=0): + """ Perform mouse click on the given QAbstractItemModel (model) and + QAbstractItemView (view) with the given row and column. + + Parameters + ---------- + model : QAbstractItemModel + Model from which QModelIndex will be obtained + view : QAbstractItemView + View from which the widget identified by the index will be + found and key sequence be performed. + index : QModelIndex + sequence : str + Sequence of characters to be inserted to the widget identifed + by the row and column. + + Raises + ------ + Disabled + If the widget cannot be edited. + LookupError + If the index cannot be located. + Note that the index error provides more + """ + check_q_model_index_valid(index) + widget = view.indexWidget(index) + if widget is None: + raise Disabled( + "No editable widget for item at row {!r} and column {!r}".format( + index.row(), index.column() + ) + ) + QTest.keyClicks(widget, sequence, delay=delay) + + +def key_click_item_view(model, view, index, key, delay=0): + """ Perform key press on the given QAbstractItemModel (model) and + QAbstractItemView (view) with the given row and column. + + Parameters + ---------- + model : QAbstractItemModel + Model from which QModelIndex will be obtained + view : QAbstractItemView + View from which the widget identified by the index will be + found and key press be performed. + index : int + key : str + Key to be pressed. + + Raises + ------ + Disabled + If the widget cannot be edited. + LookupError + If the index cannot be located. + Note that the index error provides more + """ + check_q_model_index_valid(index) + widget = view.indexWidget(index) + if widget is None: + raise Disabled( + "No editable widget for item at row {!r} and column {!r}".format( + index.row(), index.column() + ) + ) + key_click(widget, key=key, delay=delay) + + +def get_display_text_item_view(model, view, index): + """ Return the textural representation for the given model, row and column. + + Parameters + ---------- + model : QAbstractItemModel + Model from which QModelIndex will be obtained + view : QAbstractItemView + View from which the widget identified by the index will be + found and key press be performed. + index : int + + Raises + ------ + LookupError + If the index cannot be located. + Note that the index error provides more + """ + check_q_model_index_valid(index) + return model.data(index, QtCore.Qt.DisplayRole) + + def mouse_click_combobox(combobox, index, delay): """ Perform a mouse click on a QComboBox at a given index. diff --git a/traitsui/testing/tester/_ui_tester_registry/qt4/_traitsui/table_editor.py b/traitsui/testing/tester/_ui_tester_registry/qt4/_traitsui/table_editor.py new file mode 100644 index 000000000..7f5730448 --- /dev/null +++ b/traitsui/testing/tester/_ui_tester_registry/qt4/_traitsui/table_editor.py @@ -0,0 +1,104 @@ +# Copyright (c) 2005-2020, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# + +from traitsui.qt4.table_editor import SimpleEditor + +from traitsui.testing.api import ( + Cell, + DisplayedText, + MouseClick, + MouseDClick, + KeyClick, + KeySequence, + Selected, +) +from traitsui.testing.tester._ui_tester_registry._common_ui_targets import ( + BaseSourceWithLocation +) +from traitsui.testing.tester._ui_tester_registry.qt4 import ( + _interaction_helpers, + _registry_helper +) + + +class _SimpleEditorWithCell(BaseSourceWithLocation): + source_class = SimpleEditor + locator_class = Cell + handlers = [ + (MouseClick, lambda wrapper, _: wrapper._target._mouse_click( + delay=wrapper.delay)), + (KeyClick, lambda wrapper, interaction: wrapper._target._key_click( + key=interaction.key, + delay=wrapper.delay,)), + (KeySequence, lambda wrapper, interaction: wrapper._target._key_sequence( + sequence=interaction.sequence, + delay=wrapper.delay,)), + (DisplayedText, lambda wrapper, _: wrapper._target._get_displayed_text()), + (MouseDClick, lambda wrapper, _: wrapper._target._mouse_dclick( + delay=wrapper.delay,)), + ] + + def _get_model_view_index(self): + table_view = self.source.table_view + return dict( + model=table_view.model(), + view=table_view, + index=table_view.model().index(self.location.row, self.location.column), + ) + + def _mouse_click(self, delay=0): + _interaction_helpers.mouse_click_item_view( + **self._get_model_view_index(), + delay=delay, + ) + + def _mouse_dclick(self, delay=0): + _interaction_helpers.mouse_dclick_item_view( + **self._get_model_view_index(), + delay=delay, + ) + + def _key_sequence(self, sequence, delay=0): + _interaction_helpers.key_sequence_item_view( + **self._get_model_view_index(), + sequence=sequence, + delay=delay, + ) + + def _key_click(self, key, delay=0): + _interaction_helpers.key_click_item_view( + **self._get_model_view_index(), + key=key, + delay=delay, + ) + + def _get_displayed_text(self): + return _interaction_helpers.get_display_text_item_view( + **self._get_model_view_index(), + ) + + +def register(registry): + """ Register interactions for the given registry. + + If there are any conflicts, an error will occur. + + Parameters + ---------- + registry : TargetRegistry + The registry being registered to. + """ + _SimpleEditorWithCell.register(registry) + registry.register_interaction( + target_class=SimpleEditor, + interaction_class=Selected, + handler=lambda wrapper, _: wrapper._target.selected + ) diff --git a/traitsui/testing/tester/_ui_tester_registry/qt4/default_registry.py b/traitsui/testing/tester/_ui_tester_registry/qt4/default_registry.py index 9518a591d..c5e6b89b3 100644 --- a/traitsui/testing/tester/_ui_tester_registry/qt4/default_registry.py +++ b/traitsui/testing/tester/_ui_tester_registry/qt4/default_registry.py @@ -20,6 +20,7 @@ instance_editor, list_editor, range_editor, + table_editor, text_editor, ui_base, ) @@ -69,4 +70,7 @@ def get_default_registry(): # Editor Factory editor_factory.register(registry) + # TableEditor + table_editor.register(registry) + return registry diff --git a/traitsui/testing/tester/_ui_tester_registry/wx/_interaction_helpers.py b/traitsui/testing/tester/_ui_tester_registry/wx/_interaction_helpers.py index 7e1d5a579..5a2472cbd 100644 --- a/traitsui/testing/tester/_ui_tester_registry/wx/_interaction_helpers.py +++ b/traitsui/testing/tester/_ui_tester_registry/wx/_interaction_helpers.py @@ -40,6 +40,28 @@ def _create_event(event_type, control): return event +def _create_grid_event(event_type, control, *args, **kwargs): + """ Creates a wxGridEvent of a given type. + + Parameters + ---------- + event_type : wxEventType + The type of the event to be created + control : + The wx control the event is occurring on. + + Returns + ------- + wxGridEvent + The created event, of the given type, with the given control set as + the event object. + """ + event = wx.grid.GridEvent( + control.GetId(), event_type, control, *args, **kwargs + ) + return event + + def mouse_click(func): """ Decorator function for mouse clicks. Decorated functions will return if they are not enabled. Additionally, this handles the delay for the @@ -187,6 +209,14 @@ def mouse_click_object(control, delay): control.ProcessWindowEvent(click_event) +@mouse_click +def mouse_click_cell_in_grid(control, cell, delay): + click_event = _create_grid_event( + wx.grid.wxEVT_GRID_CELL_LEFT_CLICK, control, row=cell.row, col=cell.column + ) + control.ProcessWindowEvent(click_event) + + def mouse_click_notebook_tab_index(control, index, delay): """ Performs a mouseclick on a Noteboook List Editor on the tab specified by index. diff --git a/traitsui/testing/tester/_ui_tester_registry/wx/_traitsui/table_editor.py b/traitsui/testing/tester/_ui_tester_registry/wx/_traitsui/table_editor.py new file mode 100644 index 000000000..c872a9ce2 --- /dev/null +++ b/traitsui/testing/tester/_ui_tester_registry/wx/_traitsui/table_editor.py @@ -0,0 +1,55 @@ +# Copyright (c) 2005-2020, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# + +from traitsui.wx.table_editor import SimpleEditor + +from traitsui.testing.api import ( + Cell, + DisplayedText, + MouseClick, + MouseDClick, + KeyClick, + KeySequence, + Selected, +) +from traitsui.testing.tester._ui_tester_registry._common_ui_targets import ( + BaseSourceWithLocation +) +from traitsui.testing.tester._ui_tester_registry.wx import ( + _interaction_helpers, + _registry_helper +) + + +class _SimpleEditorWithCell(BaseSourceWithLocation): + source_class = SimpleEditor + locator_class = Cell + handlers = [ + (MouseClick, + (lambda wrapper, interaction: + _interaction_helpers.mouse_click_cell_in_grid( + control=wrapper._target.source.grid.control, + cell=wrapper._target.location, + delay=wrapper.delay))), + ] + + +def register(registry): + """ Register interactions for the given registry. + + If there are any conflicts, an error will occur. + + Parameters + ---------- + registry : TargetRegistry + The registry being registered to. + """ + _SimpleEditorWithCell.register(registry) diff --git a/traitsui/testing/tester/_ui_tester_registry/wx/default_registry.py b/traitsui/testing/tester/_ui_tester_registry/wx/default_registry.py index 34b5ffa65..e2d9bca07 100644 --- a/traitsui/testing/tester/_ui_tester_registry/wx/default_registry.py +++ b/traitsui/testing/tester/_ui_tester_registry/wx/default_registry.py @@ -20,6 +20,7 @@ instance_editor, list_editor, range_editor, + table_editor, text_editor, ui_base, ) @@ -69,4 +70,7 @@ def get_default_registry(): # Editor Factory editor_factory.register(registry) + # TableEditor + table_editor.register(registry) + return registry diff --git a/traitsui/testing/tester/command.py b/traitsui/testing/tester/command.py index f64ce49c6..6963d915b 100644 --- a/traitsui/testing/tester/command.py +++ b/traitsui/testing/tester/command.py @@ -28,6 +28,17 @@ class MouseClick: pass +class MouseDClick: + """ An object representing the user double clicking a mouse button. + Currently the left mouse button is assumed. + + In most circumstances, a widget can still be clicked on even if it is + disabled. Therefore unlike key events, if the widget is disabled, + implementations should not raise an exception. + """ + pass + + class KeySequence: """ An object representing the user typing a sequence of keys. diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index ff72e396c..55a4af296 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -18,6 +18,23 @@ """ +class Cell: + """ A locator for locating a target uniquely specified by a row index and a + column index. + + Attributes + ---------- + row : int + 0-based index + column : int + 0-based index + """ + + def __init__(self, row, column): + self.row = row + self.column = column + + class Index: """ A locator for locating a target that is uniquely specified by a single 0-based index. diff --git a/traitsui/testing/tester/query.py b/traitsui/testing/tester/query.py index c46219cd1..0c45bae7d 100644 --- a/traitsui/testing/tester/query.py +++ b/traitsui/testing/tester/query.py @@ -17,6 +17,16 @@ """ +class Selected: + """ An object representing an interaction to obtain the object which is + currently selected. + + Implementations should return the selected object, or None if nothing is + selected. + """ + pass + + class SelectedText: """ An object representing an interaction to obtain the displayed (echoed) plain text which is currently selected. diff --git a/traitsui/tests/editors/test_table_editor.py b/traitsui/tests/editors/test_table_editor.py index 137218bc3..3848fa5dd 100644 --- a/traitsui/tests/editors/test_table_editor.py +++ b/traitsui/tests/editors/test_table_editor.py @@ -6,15 +6,21 @@ from traitsui.api import Action, EvalTableFilter, Item, ObjectColumn, TableEditor, View from traitsui.tests._tools import ( BaseTestMixin, - create_ui, is_qt, is_wx, - process_cascade_events, press_ok_button, requires_toolkit, - reraise_exceptions, ToolkitName, ) +from traitsui.testing.api import ( + Cell, + DisplayedText, + KeySequence, + KeyClick, + MouseClick, + Selected, + UITester, +) class ListItem(HasTraits): @@ -61,11 +67,12 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), ], - filter=EvalTableFilter(expression="other_value >= 2"), + filter=EvalTableFilter(expression="other_value > 4"), ), ), buttons=["OK"], @@ -76,6 +83,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -92,6 +100,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ObjectColumn(name="value")], selection_mode="rows", selected="selections", @@ -105,6 +114,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -121,6 +131,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -137,6 +148,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -153,6 +165,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -169,6 +182,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -185,6 +199,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -201,6 +216,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -217,6 +233,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -233,6 +250,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -249,6 +267,7 @@ class ObjectList(HasTraits): "values", show_label=False, editor=TableEditor( + sortable=False, columns=[ ObjectColumn(name="value"), ObjectColumn(name="other_value"), @@ -274,46 +293,42 @@ def test_table_editor(self): object_list = ObjectListWithSelection( values=[ListItem(value=str(i ** 2)) for i in range(10)] ) - - with reraise_exceptions(), \ - create_ui(object_list, dict(view=simple_view)) as ui: - process_cascade_events() + tester = UITester() + with tester.create_ui(object_list, dict(view=simple_view)): + pass @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) def test_filtered_table_editor(self): object_list = ObjectListWithSelection( - values=[ListItem(value=str(i ** 2)) for i in range(10)] + values=[ListItem(other_value=i ** 2) for i in range(10)] ) - - with reraise_exceptions(), \ - create_ui(object_list, dict(view=filtered_view)) as ui: - process_cascade_events() - - filter = ui.get_editors("values")[0].filter - - process_cascade_events() - - self.assertIsNotNone(filter) + tester = UITester() + with tester.create_ui(object_list, dict(view=filtered_view)) as ui: + values = tester.find_by_name(ui, "values") + filter = values._target.filter + num_filtered_indices = len(values._target.filtered_indices) + self.assertIsNotNone(filter) + self.assertEqual(num_filtered_indices, 7) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) def test_table_editor_select_row(self): object_list = ObjectListWithSelection( values=[ListItem(value=str(i ** 2)) for i in range(10)] ) - object_list.selected = object_list.values[5] - with reraise_exceptions(), \ - create_ui(object_list, dict(view=select_row_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=select_row_view)) as ui: + # click the first cell in the 6th row to select the row + tester.find_by_name(ui, "values").locate(Cell(5,0)).perform(MouseClick()) + editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected elif is_wx(): selected = editor.selected_row - process_cascade_events() - self.assertIs(selected, object_list.values[5]) + self.assertIs(object_list.selected, selected) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) def test_table_editor_select_rows(self): @@ -322,17 +337,14 @@ def test_table_editor_select_rows(self): ) object_list.selections = object_list.values[5:7] - with reraise_exceptions(), \ - create_ui(object_list, dict(view=select_rows_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=select_rows_view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected elif is_wx(): selected = editor.selected_rows - process_cascade_events() - self.assertEqual(selected, object_list.values[5:7]) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) @@ -342,17 +354,14 @@ def test_table_editor_select_row_index(self): ) object_list.selected_index = 5 - with reraise_exceptions(), \ - create_ui(object_list, dict(view=select_row_index_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=select_row_index_view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected_indices elif is_wx(): selected = editor.selected_row_index - process_cascade_events() - self.assertEqual(selected, 5) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) @@ -363,17 +372,14 @@ def test_table_editor_select_row_indices(self): object_list.selected_indices = [5, 7, 8] view = select_row_indices_view - with reraise_exceptions(), \ - create_ui(object_list, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected_indices elif is_wx(): selected = editor.selected_row_indices - process_cascade_events() - self.assertEqual(selected, [5, 7, 8]) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) @@ -381,20 +387,20 @@ def test_table_editor_select_column(self): object_list = ObjectListWithSelection( values=[ListItem(value=str(i ** 2)) for i in range(10)] ) - object_list.selected_column = "value" - with reraise_exceptions(), \ - create_ui(object_list, dict(view=select_column_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=select_column_view)) as ui: + # click a cell in the first column (the "value" column) + tester.find_by_name(ui, "values").locate(Cell(0,0)).perform(MouseClick()) + editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected elif is_wx(): selected = editor.selected_column - process_cascade_events() - self.assertEqual(selected, "value") + self.assertEqual(selected, object_list.selected_column) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) def test_table_editor_select_columns(self): @@ -403,17 +409,14 @@ def test_table_editor_select_columns(self): ) object_list.selected_columns = ["value", "other_value"] - with reraise_exceptions(), \ - create_ui(object_list, dict(view=select_columns_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=select_columns_view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected elif is_wx(): selected = editor.selected_columns - process_cascade_events() - self.assertEqual(selected, ["value", "other_value"]) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) @@ -421,21 +424,21 @@ def test_table_editor_select_column_index(self): object_list = ObjectListWithSelection( values=[ListItem(value=str(i ** 2)) for i in range(10)] ) - object_list.selected_index = 1 view = select_column_index_view - with reraise_exceptions(), \ - create_ui(object_list, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=view)) as ui: + # click a cell in the index 1 column + tester.find_by_name(ui, "values").locate(Cell(0,1)).perform(MouseClick()) + editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected_indices elif is_wx(): selected = editor.selected_column_index - process_cascade_events() - self.assertEqual(selected, 1) + self.assertEqual(selected, object_list.selected_index) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) def test_table_editor_select_column_indices(self): @@ -445,17 +448,14 @@ def test_table_editor_select_column_indices(self): object_list.selected_indices = [0, 1] view = select_column_indices_view - with reraise_exceptions(), \ - create_ui(object_list, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected_indices elif is_wx(): selected = editor.selected_column_indices - process_cascade_events() - self.assertEqual(selected, [0, 1]) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) @@ -463,20 +463,122 @@ def test_table_editor_select_cell(self): object_list = ObjectListWithSelection( values=[ListItem(value=str(i ** 2)) for i in range(10)] ) - object_list.selected_cell = (object_list.values[5], "value") - with reraise_exceptions(), \ - create_ui(object_list, dict(view=select_cell_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=select_cell_view)) as ui: + # click the cell at (5,0) + tester.find_by_name(ui, "values").locate(Cell(5,0)).perform(MouseClick()) + editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected elif is_wx(): selected = editor.selected_cell - process_cascade_events() - self.assertEqual(selected, (object_list.values[5], "value")) + self.assertEqual(selected, object_list.selected_cell) + + @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) + def test_table_editor_select_row_index_with_tester(self): + object_list = ObjectListWithSelection( + values=[ListItem(value=str(i ** 2)) for i in range(10)] + ) + view = View( + Item( + "values", + show_label=False, + editor=TableEditor( + sortable=False, # switch off sorting by first column + columns=[ + ObjectColumn(name="value"), + ObjectColumn(name="other_value"), + ], + selection_mode="row", + selected="selected", + ), + ), + ) + tester = UITester() + with tester.create_ui(object_list, dict(view=view)) as ui: + wrapper = tester.find_by_name(ui, "values") + + wrapper.locate(Cell(5, 0)).perform(MouseClick()) + self.assertEqual(object_list.selected.value, str(5 ** 2)) + + wrapper.locate(Cell(6, 0)).perform(MouseClick()) + self.assertEqual(object_list.selected.value, str(6 ** 2)) + + @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) + def test_table_editor_modify_cell_with_tester(self): + object_list = ObjectListWithSelection( + values=[ListItem(value=str(i ** 2)) for i in range(10)] + ) + view = View( + Item( + "values", + show_label=False, + editor=TableEditor( + sortable=False, # switch off sorting by first column + columns=[ + ObjectColumn(name="value"), + ObjectColumn(name="other_value"), + ], + selection_mode="row", + selected="selected", + ), + ), + ) + tester = UITester() + with tester.create_ui(object_list, dict(view=view)) as ui: + wrapper = tester.find_by_name(ui, "values").locate(Cell(5, 0)) + wrapper.perform(MouseClick()) # activate edit mode + wrapper.perform(KeySequence("abc")) + self.assertEqual(object_list.selected.value, "abc") + + # second column refers to an Int type + original = object_list.selected.other_value + wrapper = tester.find_by_name(ui, "values").locate(Cell(5, 1)) + wrapper.perform(MouseClick()) + wrapper.perform(KeySequence("abc")) # invalid + self.assertEqual(object_list.selected.other_value, original) + + for _ in range(3): + wrapper.perform(KeyClick("Backspace")) + wrapper.perform(KeySequence("12")) # now ok + self.assertEqual(object_list.selected.other_value, 12) + + @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) + def test_table_editor_check_display_with_tester(self): + object_list = ObjectListWithSelection( + values=[ListItem(other_value=0)] + ) + tester = UITester() + with tester.create_ui(object_list, dict(view=select_row_view)) as ui: + wrapper = tester.find_by_name(ui, "values").locate(Cell(0, 1)) + + actual = wrapper.inspect(DisplayedText()) + self.assertEqual(actual, "0") + + object_list.values[0].other_value = 123 + + actual = wrapper.inspect(DisplayedText()) + self.assertEqual(actual, "123") + + @requires_toolkit([ToolkitName.qt]) + def test_table_editor_escape_retain_edit(self): + object_list = ObjectListWithSelection( + values=[ListItem(other_value=0)] + ) + tester = UITester() + with tester.create_ui(object_list, dict(view=select_row_view)) as ui: + cell = tester.find_by_name(ui, "values").locate(Cell(0, 1)) + + cell.perform(MouseClick()) + cell.perform(KeySequence("123")) + #cell.perform(KeyClick("Esc")) # exit edit mode, did not revert + + self.assertEqual(object_list.values[0].other_value, 123) + @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) def test_table_editor_select_cells(self): @@ -489,17 +591,14 @@ def test_table_editor_select_cells(self): (object_list.values[8], "value"), ] - with reraise_exceptions(), \ - create_ui(object_list, dict(view=select_cells_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=select_cells_view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected elif is_wx(): selected = editor.selected_cells - process_cascade_events() - self.assertEqual(selected, [ (object_list.values[5], "value"), (object_list.values[6], "other value"), @@ -511,21 +610,20 @@ def test_table_editor_select_cell_index(self): object_list = ObjectListWithSelection( values=[ListItem(value=str(i ** 2)) for i in range(10)] ) - object_list.selected_cell_index = (5, 1) view = select_cell_index_view - with reraise_exceptions(), \ - create_ui(object_list, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=view)) as ui: + # click the cell at (5,1) + tester.find_by_name(ui, "values").locate(Cell(5,1)).perform(MouseClick()) editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected_indices elif is_wx(): selected = editor.selected_cell_index - process_cascade_events() - self.assertEqual(selected, (5, 1)) + self.assertEqual(selected, object_list.selected_cell_index) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) def test_table_editor_select_cell_indices(self): @@ -535,17 +633,14 @@ def test_table_editor_select_cell_indices(self): object_list.selected_cell_indices = [(5, 0), (6, 1), (8, 0)] view = select_cell_indices_view - with reraise_exceptions(), \ - create_ui(object_list, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() if is_qt(): selected = editor.selected_indices elif is_wx(): selected = editor.selected_cell_indices - process_cascade_events() - self.assertEqual(selected, [(5, 0), (6, 1), (8, 0)]) @requires_toolkit([ToolkitName.qt]) @@ -568,10 +663,9 @@ def test_progress_column(self): object_list = ObjectList( values=[ListItem(value=str(i ** 2)) for i in range(10)] ) - - with reraise_exceptions(), \ - create_ui(object_list, dict(view=progress_view)) as ui: - process_cascade_events() + tester = UITester() + with tester.create_ui(object_list, dict(view=progress_view)) as ui: + pass @requires_toolkit([ToolkitName.qt]) def test_on_perform_action(self): @@ -583,10 +677,9 @@ def test_on_perform_action(self): mock_function = Mock() action = Action(on_perform=mock_function) - with reraise_exceptions(), \ - create_ui(object_list, dict(view=simple_view)) as ui: + tester = UITester() + with tester.create_ui(object_list, dict(view=simple_view)) as ui: editor = ui.get_editors("values")[0] - process_cascade_events() editor.set_menu_context(None, None, None) editor.perform(action) mock_function.assert_called_once()