Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions integrationtests/test_all_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
reraise_exceptions,
ToolkitName,
)
from traitsui.testing.api import command, locator, query, UITester

# This test file is not distributed nor is it in a package.
HERE = os.path.dirname(__file__)
Expand Down Expand Up @@ -279,6 +280,14 @@ def run_file(file_path):
exec(content, globals)


def load_demo(file_path, variable_name="demo"):
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
globals_ = globals().copy()
exec(content, globals_)
return globals_[variable_name]


# =============================================================================
# Test cases
# =============================================================================
Expand Down Expand Up @@ -317,3 +326,154 @@ def test_run(self):
reason=reason, file_path=file_path
)
)


class TestInteractExample(unittest.TestCase):
""" Test examples with more interactions."""

@requires_toolkit([ToolkitName.qt, ToolkitName.wx])
def test_run_auto_editable_readonly_table_cells(self):
# Test Auto_editable_readonly_table_cells in examples/demos/Advanced
filepath = os.path.join(
DEMO, "Advanced", "Auto_editable_readonly_table_cells.py"
)
demo = load_demo(filepath)
tester = UITester()
with tester.create_ui(demo) as ui:
range_editor = tester.find_by_name(ui, "max_n")
text = range_editor.locate(locator.WidgetType.textbox)
text.perform(command.KeySequence("\b\b3"))
text.perform(command.KeyClick("Enter"))

self.assertEqual(demo.max_n, 3)

slider = range_editor.locate(locator.WidgetType.slider)
for _ in range(5):
slider.perform(command.KeyClick("Right"))

self.assertEqual(demo.max_n, 53)

text.perform(command.KeySequence("\b\b\b40"))
text.perform(command.KeyClick("Enter"))

for _ in range(5):
slider.perform(command.KeyClick("Left"))

self.assertEqual(demo.max_n, 1)

@requires_toolkit([ToolkitName.qt])
def test_run_list_editors_demo(self):
# Test List_editors_demo in examples/demos/Advanced
filepath = os.path.join(
DEMO, "Advanced", "List_editors_demo.py"
)
demo = load_demo(filepath)
tester = UITester()
with tester.create_ui(demo) as ui:
main_tab = tester.find_by_id(ui, "splitter")

# List tab
main_tab.locate(locator.Index(1)).perform(command.MouseClick())
item = tester.find_by_id(ui, "list").locate(locator.Index(7))
item.find_by_name("name").perform(
command.KeySequence("\b\b\b\b\b\bDavid")
)
self.assertEqual(demo.people[7].name, "David")

# Notebook tab
main_tab.locate(locator.Index(2)).perform(command.MouseClick())
notebook = tester.find_by_id(ui, "notebook")
notebook.locate(locator.Index(1)).perform(command.MouseClick())
name_field = notebook.locate(locator.Index(1)).find_by_name("name")
name_field.perform(command.KeySequence("\b\b\b\bSimon"))
self.assertEqual(demo.people[1].name, "Simon")

# Table tab
main_tab.locate(locator.Index(0)).perform(command.MouseClick())
table = tester.find_by_id(ui, "table")

# Pick a person object
person = demo.people[6]

# Find the row that refers to this object.
# The view has sorted the items.
for i in range(len(demo.people)):
name_cell = table.locate(locator.Cell(i, 0))
displayed = name_cell.inspect(query.DisplayedText())
if displayed == person.name:
break
else:
self.fail(
"Could not find the row for {!r}".format(person.name)
)

age_cell = table.locate(locator.Cell(i, 1))
age_cell.perform(command.MouseClick())
age_cell.perform(command.KeySequence("50"))
self.assertEqual(person.age, 50)

@requires_toolkit([ToolkitName.qt])
def test_run_tree_editor_demo(self):
# Test TreeEditor_demo in examples/demo/Standard_Editors
filepath = os.path.join(
DEMO, "Standard_Editors", "TreeEditor_demo.py"
)
demo = load_demo(filepath)
tester = UITester()
with tester.create_ui(demo) as ui:
root_actor = tester.find_by_name(ui, "company")

# Enthought->Department->Business->(First employee)
node = root_actor.locate(locator.TreeNode((0, 0, 0, 0), 0))
node.perform(command.MouseClick())

name_actor = root_actor.find_by_name("name")
name_actor.perform(command.KeySequence("\b\b\b\b\bJames"))
self.assertEqual(
demo.company.departments[0].employees[0].name,
"James",
)

# Enthought->Department->Scientific
demo.company.departments[1].name = "Scientific Group"
node = root_actor.locate(locator.TreeNode((0, 0, 1), 0))
self.assertEqual(
node.inspect(query.DisplayedText()), "Scientific Group"
)

# Enthought->Department->Business
node = root_actor.locate(locator.TreeNode((0, 0, 0), 0))
node.perform(command.MouseClick())
node.perform(command.MouseDClick())

name_actor = root_actor.find_by_name("name")
name_actor.perform(command.KeySequence(" Group"))
self.assertEqual(
demo.company.departments[0].name,
"Business Group",
)

@requires_toolkit([ToolkitName.qt, ToolkitName.wx])
def test_converter(self):
# Test converter.py in examples/demo/Applications
filepath = os.path.join(
DEMO, "Applications", "converter.py"
)
demo = load_demo(filepath, "popup")
tester = UITester()
with tester.create_ui(demo) as ui:
input_amount = tester.find_by_name(ui, "input_amount")
output_amount = tester.find_by_name(ui, "output_amount")

input_amount.perform(command.KeySequence("\b\b\b\b14.0"))
self.assertEqual(
output_amount.inspect(query.DisplayedText())[:4],
"1.16",
)

tester.find_by_id(ui, "Undo").perform(command.MouseClick())

self.assertEqual(
output_amount.inspect(query.DisplayedText()),
"1.0",
)
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,4 @@ def _get_factors(self):

# Run the demo (if invoked from the command line):
if __name__ == '__main__':
demo.configure_traits()
demo.configure_traits()
2 changes: 2 additions & 0 deletions traitsui/qt4/ui_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ def _size_hint_wrapper(f, ui):

def sizeHint():
size = f()
if ui.view is None:
return size
if ui.view.width > 0:
size.setWidth(ui.view.width)
if ui.view.height > 0:
Expand Down
Empty file added traitsui/testing/__init__.py
Empty file.
25 changes: 25 additions & 0 deletions traitsui/testing/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from traitsui.testing.exceptions import ( # noqa: F401
Disabled,
)
from traitsui.testing import command # noqa: F401
from traitsui.testing.command import ( # noqa: F401
KeyClick,
KeySequence,
MouseClick,
)
from traitsui.testing import locator # noqa: F401
from traitsui.testing.locator import ( # noqa: F401
Cell,
Index,
)
from traitsui.testing import query # noqa: F401
from traitsui.testing.query import ( # noqa: F401
DisplayedText,
)
from traitsui.testing.interactor_registry import ( # noqa: F401
InteractionRegistry,
)

from traitsui.testing.ui_tester import ( # noqa: F401
UITester,
)
56 changes: 56 additions & 0 deletions traitsui/testing/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
""" This module defines action objects that can be passed to
``UITester.perform`` where the actions represent 'commands'.

Implementations for these actions are expected to produce the
documented side effects without returning any values.
"""


class MouseClick:
""" An object representing the user 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 MouseDClick:
""" An object representing the user double clicking a mouse button.
Currently the left mouse button is assumed.
"""
pass


class KeySequence:
""" An object representing the user typing a sequence of keys.

Implementations should raise ``Disabled`` if the widget is disabled.

Attribute
---------
sequence : str
A string that represents a sequence of key inputs.
e.g. "Hello World"
"""

def __init__(self, sequence):
self.sequence = sequence


class KeyClick:
""" An object representing the user clicking a key on the keyboard.

Implementations should raise ``Disabled`` if the widget is disabled.

Attribute
---------
key : str
Standardized (pyface) name for a keyboard event.
e.g. "Enter", "Tab", "Space", "0", "1", "A", ...
"""

def __init__(self, key):
self.key = key
97 changes: 97 additions & 0 deletions traitsui/testing/default_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import importlib

from pyface.base_toolkit import find_toolkit

from traitsui.ui import UI
from traitsui.testing import locator, command
from traitsui.testing.interactor_registry import InteractionRegistry


def get_default_registries():
# side-effect to determine current toolkit
package = find_toolkit("traitsui.testing")
module = importlib.import_module(".default_registry", package.__name__)
return [
module.get_default_registry(),
]


def _get_editor_by_id(ui, id):
""" Return aan editor identified by a id.

Parameters
----------
ui : traitsui.ui.UI
The UI from which an editor will be retrieved.
id : str
Id for finding an item in the UI.
"""
try:
editor = getattr(ui.info, id)
except AttributeError:
raise ValueError(
"No editors found with id {!r}. Got these: {!r}".format(
id, ui._names)
)
return editor


def _get_editor_by_name(ui, name):
""" Return a single Editor from an instance of traitsui.ui.UI with
a given extended name. Raise if zero or many editors are found.

Parameters
----------
ui : traitsui.ui.UI
The UI from which an editor will be retrieved.
name : str
A single name for retreiving an editor on a UI.

Returns
-------
editor : Editor
The single editor found.
"""
editors = ui.get_editors(name)

all_names = [editor.name for editor in ui._editors]
if not editors:
raise ValueError(
"No editors can be found with name {!r}. "
"Found these: {!r}".format(name, all_names)
)
if len(editors) > 1:
raise ValueError(
"Found multiple editors with name {!r}.".format(name))
editor, = editors
return editor


def _resolve_ui_editor_by_id(wrapper, location):
return _get_editor_by_id(wrapper.editor, location.id)


def _resolve_ui_editor_by_name(wrapper, location):
return _get_editor_by_name(wrapper.editor, location.name)


def get_ui_registry():
""" Return a registry for traitsui.ui.UI only.
"""
registry = InteractionRegistry()
registry.register_location_solver(
target_class=UI,
locator_class=locator.TargetById,
solver=_resolve_ui_editor_by_id,
)
registry.register_location_solver(
target_class=UI,
locator_class=locator.TargetByName,
solver=_resolve_ui_editor_by_name,
)
registry.register_location_solver(
target_class=UI,
locator_class=locator.NestedUI,
solver=lambda wrapper, _: wrapper.editor,
)
return registry
Loading