Skip to content

Commit

Permalink
UITester support for TableEditor (#1707)
Browse files Browse the repository at this point in the history
* initial qt implementation of table editor, updating some tests, and adding needed commands queries and locators

* inital attempts at a wx implementation (all stille very ugly as tests are allowed failures already)

* use UI Tester for all tests ui creation, disposal, and event processing

* set all views to have sortable = false for consistency (should add a specific test for sorting)

* update test_table_editor_select_row to use UI Tester to select row

* use UI Tester for all single selections

* first pass of a test for TableEditor_demo.py

* flake8

* update some comments

* remove start of wx support

* require qt on test_TableEditor_demo.py

* TableEditor tests all require qt

* update a couple of tests to use Selected

* use UITester to find editor and remove wx conditional block

* More cases of using UITester to get an editor, and removing wx conditional blocks

* typo and style

* uncomment esc line in test

* no longer need is_qt and is_wx, also couple more uses of UITester to find editor

* remove redundant test

* remove redundant view definition

* add test using MouseDClick

* add SelectedIndices query class

* Docstring re-wording

Co-authored-by: Poruri Sai Rahul <rporuri@enthought.com>

* dont import from api internally

* Add Cell and MouseDClick to the api module docstring

* update SelectedIndices api

* add news fragment

* update Selected to also return list

* upadte query object docstrings

Co-authored-by: Poruri Sai Rahul <rporuri@enthought.com>
  • Loading branch information
aaronayres35 and Poruri Sai Rahul committed Feb 3, 2022
1 parent e27a1a3 commit 360ca46
Show file tree
Hide file tree
Showing 10 changed files with 606 additions and 193 deletions.
1 change: 1 addition & 0 deletions docs/releases/upcoming/1707.feature.rst
@@ -0,0 +1 @@
Add UITester support for qt TableEditor (#1707)
@@ -0,0 +1,51 @@
"""
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
)
from traitsui.tests._tools import requires_toolkit, ToolkitName

#: 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):

@requires_toolkit([ToolkitName.qt])
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 selects 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)
)
25 changes: 22 additions & 3 deletions traitsui/testing/api.py
Expand Up @@ -22,18 +22,23 @@
- :class:`~.KeyClick`
- :class:`~.KeySequence`
- :class:`~.MouseClick`
- :class:`~.MouseDClick`
Interactions (for getting GUI states)
-------------------------------------
- :class:`~.DisplayedText`
- :class:`~.IsChecked`
- :class:`~.IsEnabled`
- :class:`~.IsVisible`
- :class:`~.Selected`
- :class:`~.SelectedIndices`
- :class:`~.SelectedText`
Locations (for locating GUI elements)
-------------------------------------
- :class:`~.Cell`
- :class:`~.Index`
- :class:`~.Slider`
- :class:`~.TargetById`
Expand Down Expand Up @@ -61,19 +66,33 @@
from .tester.ui_tester import UITester

# Interactions (for changing GUI states)
from .tester.command import MouseClick, KeyClick, KeySequence
from .tester.command import (
MouseClick,
MouseDClick,
KeyClick,
KeySequence
)

# Interactions (for getting GUI states)
from .tester.query import (
DisplayedText,
IsChecked,
IsEnabled,
IsVisible,
SelectedText,
Selected,
SelectedIndices,
SelectedText
)

# Locations (for locating GUI elements)
from .tester.locator import Index, TargetById, TargetByName, Textbox, Slider
from .tester.locator import (
Cell,
Index,
TargetById,
TargetByName,
Textbox,
Slider
)

# Advanced usage
from .tester.target_registry import TargetRegistry
Expand Down
Expand Up @@ -187,7 +187,7 @@ def mouse_click_item_view(model, view, index, delay):
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.
found and mouse click be performed.
index : QModelIndex
Raises
Expand All @@ -207,6 +207,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 mouse double click 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 Key Sequence 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.
Expand Down
@@ -0,0 +1,141 @@
# (C) Copyright 2004-2021 Enthought, Inc., Austin, TX
# 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.tester.command import (
MouseClick,
MouseDClick,
KeyClick,
KeySequence,
)
from traitsui.testing.tester.locator import Cell
from traitsui.testing.tester.query import (
DisplayedText,
Selected,
SelectedIndices,
)
from traitsui.testing.tester._ui_tester_registry._common_ui_targets import (
BaseSourceWithLocation
)
from traitsui.testing.tester._ui_tester_registry.qt4 import (
_interaction_helpers
)


def _query_table_editor_selected(wrapper, interaction):
selected = wrapper._target.selected
if not isinstance(selected, list):
if selected is None:
return []
else:
return [selected]
else:
return selected


def _query_table_editor_selected_indices(wrapper, interaction):
selected_indices = wrapper._target.selected_indices
if not isinstance(selected_indices, list):
if selected_indices == -1:
return []
else:
return [selected_indices]
else:
return selected_indices


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=_query_table_editor_selected
)
registry.register_interaction(
target_class=SimpleEditor,
interaction_class=SelectedIndices,
handler=_query_table_editor_selected_indices
)

0 comments on commit 360ca46

Please sign in to comment.