Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce toolkit-agnostic API for GUI testing #861

Closed
wants to merge 16 commits into from
Closed
25 changes: 25 additions & 0 deletions traitsui/qt4/enum_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from .constants import OKColor, ErrorColor
from .editor import Editor

from traitsui.testing.api import BaseSimulator, Disabled, simulate
from traitsui.testing.simulation import DEFAULT_REGISTRY

# default formatting function (would import from string, but not in Python 3)
capitalize = lambda s: s.capitalize()
Expand Down Expand Up @@ -299,6 +301,29 @@ def update_autoset_text_object(self):
return self.update_text_object(text)


@simulate(SimpleEditor, registry=DEFAULT_REGISTRY)
class SimpleEnumEditorSimulator(BaseSimulator):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I don't like having a simulator in between editors - it makes simulators harder to find and is an interruption if just looking at the editors. I think it would be better to have all simulators at the very end of the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I will move them down.

""" A simulator for testing GUI components with the simple EnumEditor.

See ``traitsui.testing.api``.
"""

def click_index(self, index):
self.editor.control.setCurrentIndex(index)

def set_text(self, text, confirmed=True):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think confirming text entry should be a separate function because it is a separate action for the user - pressing enter or removing focus. And I would say that testing for no change before confirmation, then checking the value after confirmation would be useful for quite a few editors.

if not self.editor.control.isEditable():
raise Disabled("This combox box is not editable by text.")

self.editor.control.setEditText(text)
line_edit = self.editor.control.lineEdit()
if line_edit is not None and confirmed:
line_edit.editingFinished.emit()

def get_text(self):
return self.editor.control.currentText()


class RadioEditor(BaseEditor):
""" Enumeration editor, used for the "custom" style, that displays radio
buttons.
Expand Down
30 changes: 30 additions & 0 deletions traitsui/qt4/instance_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
the PyQt user interface toolkit..
"""

import contextlib

from pyface.qt import QtCore, QtGui

Expand All @@ -32,6 +33,9 @@
from .constants import DropColor
from .helper import position_window

from traitsui.testing.api import BaseSimulator, simulate
from traitsui.testing.simulation import DEFAULT_REGISTRY


OrientationMap = {
"default": None,
Expand Down Expand Up @@ -396,6 +400,17 @@ def _view_changed(self, view):
self.resynch_editor()


@simulate(CustomEditor, registry=DEFAULT_REGISTRY)
class CustomInstanceEditorSimulator(BaseSimulator):
""" A simulator for custom instance editor: it delegates commands and
queries to the internal UI panel.
"""

@contextlib.contextmanager
def get_ui(self):
yield self.editor._ui


class SimpleEditor(CustomEditor):
""" Simple style of editor for instances, which displays a button. Clicking
the button displays a dialog box in which the instance can be edited.
Expand Down Expand Up @@ -463,3 +478,18 @@ def _parent_closed(self):
self._dialog_ui.control.close()
self._dialog_ui.dispose()
self._dialog_ui = None


@simulate(SimpleEditor, registry=DEFAULT_REGISTRY)
class SimpleInstanceEditorSimulator(BaseSimulator):
""" A simulator for simple instance editor. It launches the dialog and
delegates commands and queries to the dialog UI.
"""

@contextlib.contextmanager
def get_ui(self):
self.editor._button.click()
try:
yield self.editor._dialog_ui
finally:
self.editor._dialog_ui.dispose()
25 changes: 25 additions & 0 deletions traitsui/qt4/text_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

from .constants import OKColor

from traitsui.testing.api import BaseSimulator, Disabled, simulate
from traitsui.testing.simulation import DEFAULT_REGISTRY


class SimpleEditor(Editor):
Expand Down Expand Up @@ -182,6 +184,29 @@ class CustomEditor(SimpleEditor):
base_style = QtGui.QTextEdit


@simulate(CustomEditor, registry=DEFAULT_REGISTRY)
@simulate(SimpleEditor, registry=DEFAULT_REGISTRY)
class TextEditorSimulator(BaseSimulator):
""" A simulator for testing GUI components with the simple and custom
styled TextEditor.

See ``traitsui.testing.api``.
"""

def set_text(self, text, confirmed=True):
if not self.editor.control.isEnabled():
raise Disabled("Text field is disabled.")

self.editor.control.setText(text)

factory = self.editor.factory
if factory.auto_set and not factory.is_grid_cell and confirmed:
self.editor.control.textEdited.emit(text)

def get_text(self):
return self.editor.control.text()


class ReadonlyEditor(BaseReadonlyEditor):
""" Read-only style of text editor, which displays a read-only text field.
"""
Expand Down
Empty file added traitsui/testing/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions traitsui/testing/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from traitsui.testing.ui_tester import UITester # noqa: F401
from traitsui.testing.exceptions import Disabled # noqa: F401
from traitsui.testing.simulation import (
BaseSimulator,
DEFAULT_REGISTRY,
simulate,
SimulatorRegistry,
)
10 changes: 10 additions & 0 deletions traitsui/testing/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

class SimulationError(Exception):
""" Raised when simulating user interactions on GUI."""
pass


class Disabled(SimulationError):
""" Raised when a simulation fails because the widget is disabled.
"""
pass
Loading