From ba2378f2f5d38057e13ff0d72ca5870f9ac0e84c Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 19 Aug 2020 15:34:43 -0500 Subject: [PATCH 01/60] first round of updates for UI Tester (default_registry toolkit setup and added delay) --- traitsui/testing/tester/default_registry.py | 22 ++++++++++++++++++ traitsui/testing/tester/qt4/__init__.py | 0 .../testing/tester/qt4/default_registry.py | 17 ++++++++++++++ .../tester/tests/test_default_registry.py | 23 +++++++++++++++++++ traitsui/testing/tester/ui_tester.py | 6 ++++- traitsui/testing/tester/ui_wrapper.py | 4 +++- traitsui/testing/tester/wx/__init__.py | 0 .../testing/tester/wx/default_registry.py | 17 ++++++++++++++ 8 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 traitsui/testing/tester/default_registry.py create mode 100644 traitsui/testing/tester/qt4/__init__.py create mode 100644 traitsui/testing/tester/qt4/default_registry.py create mode 100644 traitsui/testing/tester/tests/test_default_registry.py create mode 100644 traitsui/testing/tester/wx/__init__.py create mode 100644 traitsui/testing/tester/wx/default_registry.py diff --git a/traitsui/testing/tester/default_registry.py b/traitsui/testing/tester/default_registry.py new file mode 100644 index 000000000..a65cf6af6 --- /dev/null +++ b/traitsui/testing/tester/default_registry.py @@ -0,0 +1,22 @@ +# 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! +# + +import importlib + +from traits.etsconfig.api import ETSConfig + +from traitsui.testing.tester.registry import TargetRegistry + + +def get_default_registry(): + # side-effect to determine current toolkit + module = importlib.import_module(".default_registry", "traitsui.testing.tester." + ETSConfig.toolkit) + return module.get_default_registry() diff --git a/traitsui/testing/tester/qt4/__init__.py b/traitsui/testing/tester/qt4/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py new file mode 100644 index 000000000..64c233d55 --- /dev/null +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -0,0 +1,17 @@ +# 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.testing.tester.registry import TargetRegistry + + +def get_default_registry(): + registry = TargetRegistry() + return registry diff --git a/traitsui/testing/tester/tests/test_default_registry.py b/traitsui/testing/tester/tests/test_default_registry.py new file mode 100644 index 000000000..54e364dc3 --- /dev/null +++ b/traitsui/testing/tester/tests/test_default_registry.py @@ -0,0 +1,23 @@ +# 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! +# + +import unittest + +from traitsui.testing.tester.default_registry import get_default_registry +from traitsui.testing.tester.registry import TargetRegistry + + +class TestDefaultRegistry(unittest.TestCase): + + def test_load_default_registries(self): + registry = get_default_registry() + self.assertIsInstance(registry, TargetRegistry) + \ No newline at end of file diff --git a/traitsui/testing/tester/ui_tester.py b/traitsui/testing/tester/ui_tester.py index 80ce53b2f..4ba6ba0dd 100644 --- a/traitsui/testing/tester/ui_tester.py +++ b/traitsui/testing/tester/ui_tester.py @@ -11,6 +11,7 @@ from traitsui.ui import UI from traitsui.testing.tester import locator +from traitsui.testing.tester.default_registry import get_default_registry from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.ui_wrapper import UIWrapper from traitsui.tests._tools import ( @@ -131,7 +132,7 @@ class App(HasTraits): of decreasing priority. A shallow copy will be made. """ - def __init__(self, registries=None): + def __init__(self, registries=None, delay=0): """ Instantiate the UI tester. """ @@ -142,6 +143,8 @@ def __init__(self, registries=None): # The find_by_name method in this class depends on this registry self._registries.append(_get_ui_registry()) + self._registries.append(get_default_registry()) + self.delay = delay def create_ui(self, object, ui_kwargs=None): """ Context manager to create a UI and dispose it upon exit. @@ -179,6 +182,7 @@ def find_by_name(self, ui, name): return UIWrapper( target=ui, registries=self._registries, + delay=self.delay, ).find_by_name(name=name) diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index 9be9d8198..efdf34d8b 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -49,7 +49,7 @@ class UIWrapper: a leaf target that can be operated on. """ - def __init__(self, target, registries): + def __init__(self, target, registries, delay=0): """ Initializer Parameters @@ -63,6 +63,7 @@ def __init__(self, target, registries): """ self.target = target self._registries = registries + self.delay = delay def locate(self, location): """ Attempt to resolve the given location and return a new @@ -80,6 +81,7 @@ def locate(self, location): return UIWrapper( target=self._get_next_target(location), registries=self._registries, + delay=self.delay, ) def find_by_name(self, name): diff --git a/traitsui/testing/tester/wx/__init__.py b/traitsui/testing/tester/wx/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py new file mode 100644 index 000000000..64c233d55 --- /dev/null +++ b/traitsui/testing/tester/wx/default_registry.py @@ -0,0 +1,17 @@ +# 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.testing.tester.registry import TargetRegistry + + +def get_default_registry(): + registry = TargetRegistry() + return registry From a6d28ca7780f277ac57a439a333a893cb0491288 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 19 Aug 2020 15:48:31 -0500 Subject: [PATCH 02/60] attempt to handle Null toolkit --- traitsui/testing/tester/default_registry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/traitsui/testing/tester/default_registry.py b/traitsui/testing/tester/default_registry.py index a65cf6af6..ccf491619 100644 --- a/traitsui/testing/tester/default_registry.py +++ b/traitsui/testing/tester/default_registry.py @@ -18,5 +18,8 @@ def get_default_registry(): # side-effect to determine current toolkit - module = importlib.import_module(".default_registry", "traitsui.testing.tester." + ETSConfig.toolkit) - return module.get_default_registry() + if ETSConfig.toolkit: + module = importlib.import_module(".default_registry", "traitsui.testing.tester." + ETSConfig.toolkit) + return module.get_default_registry() + else: + return TargetRegistry() From 28d5094cb65b825e8ac9487d28985314e07af75c Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 19 Aug 2020 16:01:21 -0500 Subject: [PATCH 03/60] fixing null handling --- traitsui/testing/tester/default_registry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/traitsui/testing/tester/default_registry.py b/traitsui/testing/tester/default_registry.py index ccf491619..4172ede46 100644 --- a/traitsui/testing/tester/default_registry.py +++ b/traitsui/testing/tester/default_registry.py @@ -14,12 +14,13 @@ from traits.etsconfig.api import ETSConfig from traitsui.testing.tester.registry import TargetRegistry +from traitsui.tests._tools import is_null def get_default_registry(): # side-effect to determine current toolkit - if ETSConfig.toolkit: + if is_null(): + return TargetRegistry() + else: module = importlib.import_module(".default_registry", "traitsui.testing.tester." + ETSConfig.toolkit) return module.get_default_registry() - else: - return TargetRegistry() From 684be1cd10f70c5a9d82806f5094c4181984a78e Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 20 Aug 2020 08:25:32 -0500 Subject: [PATCH 04/60] setting up MouseClick for ButtonEditor --- .../testing/tester/qt4/default_registry.py | 25 +++++++++++++++- traitsui/testing/tester/qt4/helpers.py | 21 ++++++++++++++ .../qt4/implementation/button_editor.py | 29 +++++++++++++++++++ .../testing/tester/wx/default_registry.py | 18 ++++++++++++ traitsui/testing/tester/wx/helpers.py | 21 ++++++++++++++ .../tester/wx/implementation/button_editor.py | 22 ++++++++++++++ 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 traitsui/testing/tester/qt4/helpers.py create mode 100644 traitsui/testing/tester/qt4/implementation/button_editor.py create mode 100644 traitsui/testing/tester/wx/helpers.py create mode 100644 traitsui/testing/tester/wx/implementation/button_editor.py diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 64c233d55..2a13c361d 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -9,9 +9,32 @@ # Thanks for using Enthought open source! # -from traitsui.testing.tester.registry import TargetRegistry +from pyface.qt import QtGui +from traitsui.testing.tester import command +from traitsui.testing.tester.registry import TargetRegistry +from traitsui.testing.tester.qt4 import helpers +from traitsui.testing.tester.qt4.implementation import ( + button_editor, +) def get_default_registry(): registry = TargetRegistry() + + button_editor.register(registry) + + widget_classes = [ + QtGui.QPushButton, + ] + handlers = [ + (command.MouseClick, helpers.mouse_click_qwidget), + ] + for widget_class in widget_classes: + for interaction_class, handler in handlers: + registry.register_handler( + target_class=widget_class, + interaction_class=interaction_class, + handler=handler, + ) + return registry diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py new file mode 100644 index 000000000..06a4b2529 --- /dev/null +++ b/traitsui/testing/tester/qt4/helpers.py @@ -0,0 +1,21 @@ +# 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 pyface.qt import QtCore +from pyface.qt.QtTest import QTest + +def mouse_click_qwidget(wrapper, action): + QTest.mouseClick( + wrapper.editor, + QtCore.Qt.LeftButton, + delay=wrapper.delay, + ) \ No newline at end of file diff --git a/traitsui/testing/tester/qt4/implementation/button_editor.py b/traitsui/testing/tester/qt4/implementation/button_editor.py new file mode 100644 index 000000000..54f933e50 --- /dev/null +++ b/traitsui/testing/tester/qt4/implementation/button_editor.py @@ -0,0 +1,29 @@ +# 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.testing import locator +from traitsui.qt4.button_editor import CustomEditor, SimpleEditor + + +def register(registry): + """ Register actions for the given registry. + + If there are any conflicts, an error will occur. + """ + registry.register_solver( + target_class=SimpleEditor, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.editor.control, + ) + registry.register_solver( + target_class=CustomEditor, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.editor.control, + ) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 64c233d55..daa59d023 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -9,9 +9,27 @@ # Thanks for using Enthought open source! # +from traitsui.testing.tester import command from traitsui.testing.tester.registry import TargetRegistry +from traitsui.testing.tester.wx import helpers +from traitsui.testing.tester.wx.implementation import ( + button_editor, +) def get_default_registry(): registry = TargetRegistry() + + button_editor.register(registry) + + registry.register_handler( + target_class=wx.Button, + interaction_class=command.MouseClick, + handler=lambda wrapper, _: ( + helpers.mouse_click_button( + control=wrapper.editor, delay=wrapper.delay, + ) + ) + ) + return registry diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py new file mode 100644 index 000000000..9a84db7f9 --- /dev/null +++ b/traitsui/testing/tester/wx/helpers.py @@ -0,0 +1,21 @@ +# 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! +# + +import wx + +def mouse_click_button(control, delay): + if not control.IsEnabled(): + return + wx.MilliSleep(delay) + click_event = wx.CommandEvent( + wx.wxEVT_COMMAND_BUTTON_CLICKED, control.GetId() + ) + control.ProcessEvent(click_event) \ No newline at end of file diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py new file mode 100644 index 000000000..d8caaff03 --- /dev/null +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -0,0 +1,22 @@ +# 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.button_editor import SimpleEditor +from traitsui.testing import locator +from traitsui.testing.wx import helpers + + +def register(registry): + registry.register_solver( + target_class=SimpleEditor, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.target.control, + ) + From 9973d385fa9654f605bdd4084e4a0faa069a4ddb Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 20 Aug 2020 10:56:30 -0500 Subject: [PATCH 05/60] adding DefaultTarget locator class and logic to handle its use in ui_wrapper (and a couple simple fixes) --- traitsui/testing/tester/locator.py | 3 +++ .../tester/qt4/implementation/button_editor.py | 2 +- .../tester/tests/test_default_registry.py | 10 ++++++++++ traitsui/testing/tester/ui_wrapper.py | 18 ++++++++++++++++++ traitsui/testing/tester/wx/default_registry.py | 2 ++ .../tester/wx/implementation/button_editor.py | 4 ++-- 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index d4e0b36b1..9bcef0936 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -35,3 +35,6 @@ class TargetByName: """ def __init__(self, name): self.name = name + +class DefaultTarget: + pass diff --git a/traitsui/testing/tester/qt4/implementation/button_editor.py b/traitsui/testing/tester/qt4/implementation/button_editor.py index 54f933e50..fbe10b8aa 100644 --- a/traitsui/testing/tester/qt4/implementation/button_editor.py +++ b/traitsui/testing/tester/qt4/implementation/button_editor.py @@ -8,7 +8,7 @@ # # Thanks for using Enthought open source! # -from traitsui.testing import locator +from traitsui.testing.tester import locator from traitsui.qt4.button_editor import CustomEditor, SimpleEditor diff --git a/traitsui/testing/tester/tests/test_default_registry.py b/traitsui/testing/tester/tests/test_default_registry.py index 54e364dc3..6e8cdb08c 100644 --- a/traitsui/testing/tester/tests/test_default_registry.py +++ b/traitsui/testing/tester/tests/test_default_registry.py @@ -13,6 +13,7 @@ from traitsui.testing.tester.default_registry import get_default_registry from traitsui.testing.tester.registry import TargetRegistry +from traitsui.tests._tools import is_null class TestDefaultRegistry(unittest.TestCase): @@ -20,4 +21,13 @@ class TestDefaultRegistry(unittest.TestCase): def test_load_default_registries(self): registry = get_default_registry() self.assertIsInstance(registry, TargetRegistry) + if not is_null(): + self.assertGreaterEqual( + len(registry._interaction_registry._target_to_key_to_value), + 1, + ) + self.assertGreaterEqual( + len(registry._location_registry._target_to_key_to_value), + 1, + ) \ No newline at end of file diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index efdf34d8b..1c118e616 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -157,6 +157,24 @@ def _perform_or_inspect(self, interaction): with _event_processed(): return handler(self, interaction) + try: + # there may not be a handler registered for this interaction on + # the original target. Instead, it may be registered for DefaultTarget + default_target = self.locate(locator.DefaultTarget) + # If there we can't solve from the current target to DefaultTarget this + # interaction isn't supported and we can raise appropraite exception below + except LocationNotSupported: + pass + else: + # if we can locate a DefaultTarget from the current target, try to + # perform the interactation on it instead + try: + default_target._perform_or_inspect(interaction) + # if we can't add to the list of things which are supported for + # the DefaultTarget + except InteractionNotSupported as e: + supported.extend(e.supported) + raise InteractionNotSupported( target_class=self.target.__class__, interaction_class=interaction.__class__, diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index daa59d023..59cc23dc8 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -9,6 +9,8 @@ # Thanks for using Enthought open source! # +import wx + from traitsui.testing.tester import command from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.wx import helpers diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py index d8caaff03..99545d800 100644 --- a/traitsui/testing/tester/wx/implementation/button_editor.py +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -9,8 +9,8 @@ # Thanks for using Enthought open source! # from traitsui.wx.button_editor import SimpleEditor -from traitsui.testing import locator -from traitsui.testing.wx import helpers +from traitsui.testing.tester import locator +from traitsui.testing.tester.wx import helpers def register(registry): From 547b52b8fdc081e29d5929a15c83ce25d734be77 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 20 Aug 2020 12:05:52 -0500 Subject: [PATCH 06/60] addressing comments --- traitsui/testing/tester/default_registry.py | 20 +++++++++++++------ .../testing/tester/qt4/default_registry.py | 8 ++++++++ .../tester/tests/test_default_registry.py | 1 - .../testing/tester/tests/test_ui_tester.py | 8 ++++++++ .../testing/tester/wx/default_registry.py | 8 ++++++++ 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/traitsui/testing/tester/default_registry.py b/traitsui/testing/tester/default_registry.py index 4172ede46..fc9b8e4c7 100644 --- a/traitsui/testing/tester/default_registry.py +++ b/traitsui/testing/tester/default_registry.py @@ -11,16 +11,24 @@ import importlib -from traits.etsconfig.api import ETSConfig - from traitsui.testing.tester.registry import TargetRegistry -from traitsui.tests._tools import is_null - def get_default_registry(): + """ Creates a default registry for UITester that is toolkit specific. + + Returns + ------- + registry : TargetRegistry + The default registry containing implementations for TraitsUI editors + that is toolkit specific. + """ # side-effect to determine current toolkit - if is_null(): + from pyface.toolkit import toolkit_object + from traits.etsconfig.api import ETSConfig + + if ETSConfig.toolkit == "null": return TargetRegistry() else: - module = importlib.import_module(".default_registry", "traitsui.testing.tester." + ETSConfig.toolkit) + toolkit = {'wx': 'wx', 'qt4': 'qt4', 'qt': 'qt4'}[ETSConfig.toolkit] + module = importlib.import_module(".default_registry", "traitsui.testing.tester." + toolkit) return module.get_default_registry() diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 64c233d55..369171e2d 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -13,5 +13,13 @@ def get_default_registry(): + """ Creates a default registry for UITester that is qt specific. + + Returns + ------- + registry : TargetRegistry + The default registry containing implementations for TraitsUI editors + that is qt specific. + """ registry = TargetRegistry() return registry diff --git a/traitsui/testing/tester/tests/test_default_registry.py b/traitsui/testing/tester/tests/test_default_registry.py index 54e364dc3..9b036c1ed 100644 --- a/traitsui/testing/tester/tests/test_default_registry.py +++ b/traitsui/testing/tester/tests/test_default_registry.py @@ -20,4 +20,3 @@ class TestDefaultRegistry(unittest.TestCase): def test_load_default_registries(self): registry = get_default_registry() self.assertIsInstance(registry, TargetRegistry) - \ No newline at end of file diff --git a/traitsui/testing/tester/tests/test_ui_tester.py b/traitsui/testing/tester/tests/test_ui_tester.py index 23a27a824..700aab5e1 100644 --- a/traitsui/testing/tester/tests/test_ui_tester.py +++ b/traitsui/testing/tester/tests/test_ui_tester.py @@ -100,3 +100,11 @@ def test_multiple_editors_found(self): self.assertIn( "Found multiple editors", str(exception_context.exception), ) + + def test_delay_persisted(self): + tester = UITester(delay=.01) + view = View(Item("submit_button")) + with tester.create_ui(Order(), dict(view=view)) as ui: + wrapped = tester.find_by_name(ui,"submit_button") + self.assertEqual(wrapped.delay, .01) + diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 64c233d55..0c93d494d 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -13,5 +13,13 @@ def get_default_registry(): + """ Creates a default registry for UITester that is wx specific. + + Returns + ------- + registry : TargetRegistry + The default registry containing implementations for TraitsUI editors + that is wx specific. + """ registry = TargetRegistry() return registry From a6ede420a7c8ec3d0754a8b284ad1bf5e0757ce1 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 20 Aug 2020 12:28:13 -0500 Subject: [PATCH 07/60] this test update was accidentally left out of merge --- traitsui/testing/tester/tests/test_default_registry.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/traitsui/testing/tester/tests/test_default_registry.py b/traitsui/testing/tester/tests/test_default_registry.py index 1316001a0..4f41c8260 100644 --- a/traitsui/testing/tester/tests/test_default_registry.py +++ b/traitsui/testing/tester/tests/test_default_registry.py @@ -21,7 +21,6 @@ class TestDefaultRegistry(unittest.TestCase): def test_load_default_registries(self): registry = get_default_registry() self.assertIsInstance(registry, TargetRegistry) -<<<<<<< HEAD if not is_null(): self.assertGreaterEqual( len(registry._interaction_registry._target_to_key_to_value), @@ -32,5 +31,3 @@ def test_load_default_registries(self): 1, ) -======= ->>>>>>> ui-tester-api-updates1 From 69d5cd5a90a44ee9e86d8b20bd12f005748f7cf8 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 20 Aug 2020 13:37:51 -0500 Subject: [PATCH 08/60] adding tests (currently broken due to imports) --- .../tester/qt4/tests/test_default_registry.py | 0 .../tester/wx/tests/test_default_registry.py | 73 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 traitsui/testing/tester/qt4/tests/test_default_registry.py create mode 100644 traitsui/testing/tester/wx/tests/test_default_registry.py diff --git a/traitsui/testing/tester/qt4/tests/test_default_registry.py b/traitsui/testing/tester/qt4/tests/test_default_registry.py new file mode 100644 index 000000000..e69de29bb diff --git a/traitsui/testing/tester/wx/tests/test_default_registry.py b/traitsui/testing/tester/wx/tests/test_default_registry.py new file mode 100644 index 000000000..bcc2f0d0a --- /dev/null +++ b/traitsui/testing/tester/wx/tests/test_default_registry.py @@ -0,0 +1,73 @@ +# 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! +# + +import unittest +from unittest import mock + +from pyface.api import GUI + +from traitsui.testing.tester import command +from traitsui.testing.tester.ui_tester import UIWrapper + +try: + import wx + from traitsui.testing.tester.wx import default_registry +except ImportError: + if is_wx(): + raise +from traitsui.tests._tools import ( + is_wx, + requires_toolkit, + ToolkitName, +) + +@requires_toolkit([ToolkitName.wx]) +class TestInteractorAction(unittest.TestCase): + + def setUp(self): + self.app = wx.App() + self.frame = wx.Frame(None) + self.frame.Show() + + def tearDown(self): + wx.CallAfter(self.app.ExitMainLoop) + self.app.MainLoop() + + def test_mouse_click(self): + handler = mock.Mock() + button = wx.Button(self.frame) + button.Bind(wx.EVT_BUTTON, handler) + wrapper = UIWrapper( + target=button, + _registries=[default_registry.get_default_registry()], + ) + + # when + wrapper.perform(command.MouseClick()) + + # then + self.assertEqual(handler.call_count, 1) + + def test_mouse_click_disabled_button(self): + handler = mock.Mock() + button = wx.Button(self.frame) + button.Bind(wx.EVT_BUTTON, handler) + button.Enable(False) + wrapper = UIWrapper( + target=button, + registries=[default_registry.get_default_registry()], + ) + + # when + wrapper.perform(command.MouseClick()) + + # then + self.assertEqual(handler.call_count, 0) From b97a923e03693103300d5a237c23415163da45ac Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 20 Aug 2020 14:44:14 -0500 Subject: [PATCH 09/60] adding test for toolkit specific default registries which test mouse click --- traitsui/testing/tester/qt4/helpers.py | 2 +- .../tester/qt4/tests/test_default_registry.py | 67 +++++++++++++++++++ .../testing/tester/wx/default_registry.py | 2 +- .../tester/wx/tests/test_default_registry.py | 4 +- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 06a4b2529..b4ab3ee41 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -15,7 +15,7 @@ def mouse_click_qwidget(wrapper, action): QTest.mouseClick( - wrapper.editor, + wrapper.target, QtCore.Qt.LeftButton, delay=wrapper.delay, ) \ No newline at end of file diff --git a/traitsui/testing/tester/qt4/tests/test_default_registry.py b/traitsui/testing/tester/qt4/tests/test_default_registry.py index e69de29bb..791a17be9 100644 --- a/traitsui/testing/tester/qt4/tests/test_default_registry.py +++ b/traitsui/testing/tester/qt4/tests/test_default_registry.py @@ -0,0 +1,67 @@ +# 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! +# + +import unittest +from unittest import mock + +#from pyface.gui import GUI +from traitsui.tests._tools import ( + is_qt, + requires_toolkit, + ToolkitName, +) +from traitsui.testing.tester import command +from traitsui.testing.tester.ui_wrapper import UIWrapper + +try: + from pyface.qt import QtGui + from traitsui.testing.tester.qt4 import default_registry +except ImportError: + if is_qt(): + raise + + +@requires_toolkit([ToolkitName.qt]) +class TestInteractorAction(unittest.TestCase): + + def test_mouse_click(self): + button = QtGui.QPushButton() + click_slot = mock.Mock() + button.clicked.connect(click_slot) + + wrapper = wrapper = UIWrapper( + target=button, + registries=[default_registry.get_default_registry()], + ) + + wrapper.perform(command.MouseClick()) + + self.assertEqual(click_slot.call_count, 1) + + def test_mouse_click_disabled(self): + button = QtGui.QPushButton() + button.setEnabled(False) + + click_slot = mock.Mock() + button.clicked.connect(click_slot) + + wrapper = wrapper = UIWrapper( + target=button, + registries=[default_registry.get_default_registry()], + ) + + # when + # clicking won't fail, it just does not do anything. + # This is consistent with the actual UI. + wrapper.perform(command.MouseClick()) + + # then + self.assertEqual(click_slot.call_count, 0) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 0672d4282..3b131f6c1 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -37,7 +37,7 @@ def get_default_registry(): interaction_class=command.MouseClick, handler=lambda wrapper, _: ( helpers.mouse_click_button( - control=wrapper.editor, delay=wrapper.delay, + control=wrapper.target, delay=wrapper.delay, ) ) ) diff --git a/traitsui/testing/tester/wx/tests/test_default_registry.py b/traitsui/testing/tester/wx/tests/test_default_registry.py index bcc2f0d0a..534713d1a 100644 --- a/traitsui/testing/tester/wx/tests/test_default_registry.py +++ b/traitsui/testing/tester/wx/tests/test_default_registry.py @@ -15,7 +15,7 @@ from pyface.api import GUI from traitsui.testing.tester import command -from traitsui.testing.tester.ui_tester import UIWrapper +from traitsui.testing.tester.ui_wrapper import UIWrapper try: import wx @@ -47,7 +47,7 @@ def test_mouse_click(self): button.Bind(wx.EVT_BUTTON, handler) wrapper = UIWrapper( target=button, - _registries=[default_registry.get_default_registry()], + registries=[default_registry.get_default_registry()], ) # when From 39c034e939aea151325950550f674ff4111022d7 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 20 Aug 2020 17:03:14 -0500 Subject: [PATCH 10/60] fixing broken tests, and convert TraitsUI test for ButtonEditor to use new UITester --- .../testing/tester/qt4/default_registry.py | 28 ++++++++-- .../qt4/implementation/button_editor.py | 18 +++---- .../testing/tester/tests/test_ui_wrapper.py | 12 +++++ traitsui/testing/tester/ui_wrapper.py | 6 +-- .../testing/tester/wx/default_registry.py | 17 ++++++- .../tester/wx/implementation/button_editor.py | 15 +++--- traitsui/tests/editors/test_button_editor.py | 51 +++++++++++-------- 7 files changed, 100 insertions(+), 47 deletions(-) diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 4a59e7925..14275c478 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -11,7 +11,7 @@ from pyface.qt import QtGui -from traitsui.testing.tester import command +from traitsui.testing.tester import command, query from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.qt4 import helpers from traitsui.testing.tester.qt4.implementation import ( @@ -27,10 +27,25 @@ def get_default_registry(): The default registry containing implementations for TraitsUI editors that is qt specific. """ - registry = TargetRegistry() + registry = get_qobject_registry() button_editor.register(registry) - + + + return registry + + +def get_qobject_registry(): + """ Creates a generic registry for handling/solving qt objects. (i.e. + this registry is independent of TraitsUI) + + Returns + ------- + registry : TargetRegistry + Registry containing qt specific generic handlers and solvers. + """ + registry = TargetRegistry() + widget_classes = [ QtGui.QPushButton, ] @@ -45,4 +60,11 @@ def get_default_registry(): handler=handler, ) + registry.register_handler( + target_class=QtGui.QPushButton, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.text(), + ) + return registry + diff --git a/traitsui/testing/tester/qt4/implementation/button_editor.py b/traitsui/testing/tester/qt4/implementation/button_editor.py index fbe10b8aa..4e1d695b5 100644 --- a/traitsui/testing/tester/qt4/implementation/button_editor.py +++ b/traitsui/testing/tester/qt4/implementation/button_editor.py @@ -8,7 +8,7 @@ # # Thanks for using Enthought open source! # -from traitsui.testing.tester import locator +from traitsui.testing.tester import locator, query from traitsui.qt4.button_editor import CustomEditor, SimpleEditor @@ -17,13 +17,9 @@ def register(registry): If there are any conflicts, an error will occur. """ - registry.register_solver( - target_class=SimpleEditor, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: wrapper.editor.control, - ) - registry.register_solver( - target_class=CustomEditor, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: wrapper.editor.control, - ) + for target_class in [SimpleEditor, CustomEditor]: + registry.register_solver( + target_class=target_class, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.target.control, + ) diff --git a/traitsui/testing/tester/tests/test_ui_wrapper.py b/traitsui/testing/tester/tests/test_ui_wrapper.py index 6ae186a22..b54ffe2f7 100644 --- a/traitsui/testing/tester/tests/test_ui_wrapper.py +++ b/traitsui/testing/tester/tests/test_ui_wrapper.py @@ -123,6 +123,12 @@ def get_handler(self, target_class, interaction_class): interaction_class=None, supported=[int], ) + def get_solver(self, target_class, interaction_class): + raise LocationNotSupported( + target_class=None, + locator_class=None, + supported=[], + ) class EmptyRegistry2: def get_handler(self, target_class, interaction_class): @@ -131,6 +137,12 @@ def get_handler(self, target_class, interaction_class): interaction_class=None, supported=[float], ) + def get_solver(self, target_class, interaction_class): + raise LocationNotSupported( + target_class=None, + locator_class=None, + supported=[], + ) wrapper = example_ui_wrapper( registries=[EmptyRegistry1(), EmptyRegistry2()], diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index 1c118e616..a267988ea 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -160,16 +160,16 @@ def _perform_or_inspect(self, interaction): try: # there may not be a handler registered for this interaction on # the original target. Instead, it may be registered for DefaultTarget - default_target = self.locate(locator.DefaultTarget) + default_target = self.locate(locator.DefaultTarget()) # If there we can't solve from the current target to DefaultTarget this # interaction isn't supported and we can raise appropraite exception below - except LocationNotSupported: + except LocationNotSupported: pass else: # if we can locate a DefaultTarget from the current target, try to # perform the interactation on it instead try: - default_target._perform_or_inspect(interaction) + return default_target._perform_or_inspect(interaction) # if we can't add to the list of things which are supported for # the DefaultTarget except InteractionNotSupported as e: diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 3b131f6c1..e812f5b9e 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -28,9 +28,22 @@ def get_default_registry(): The default registry containing implementations for TraitsUI editors that is wx specific. """ - registry = TargetRegistry() + registry = get_wobject_registry() button_editor.register(registry) + + return registry + +def get_wobject_registry(): + """ Creates a generic registry for handling/solving wx objects. (i.e. + this registry is independent of TraitsUI) + + Returns + ------- + registry : TargetRegistry + Registry containing wx specific generic handlers and solvers. + """ + registry = TargetRegistry() registry.register_handler( target_class=wx.Button, @@ -41,5 +54,5 @@ def get_default_registry(): ) ) ) - + return registry diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py index 99545d800..81654ad65 100644 --- a/traitsui/testing/tester/wx/implementation/button_editor.py +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -8,15 +8,16 @@ # # Thanks for using Enthought open source! # -from traitsui.wx.button_editor import SimpleEditor -from traitsui.testing.tester import locator +from traitsui.wx.button_editor import SimpleEditor, CustomEditor +from traitsui.testing.tester import locator, query from traitsui.testing.tester.wx import helpers def register(registry): - registry.register_solver( - target_class=SimpleEditor, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: wrapper.target.control, - ) + for target_class in [SimpleEditor, CustomEditor]: + registry.register_solver( + target_class=target_class, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.target.control, + ) diff --git a/traitsui/tests/editors/test_button_editor.py b/traitsui/tests/editors/test_button_editor.py index 8640abab5..b644f071b 100644 --- a/traitsui/tests/editors/test_button_editor.py +++ b/traitsui/tests/editors/test_button_editor.py @@ -3,6 +3,7 @@ from pyface.gui import GUI from traits.api import Button, HasTraits, List, Str +from traits.testing.api import UnittestTools from traitsui.api import ButtonEditor, Item, UItem, View from traitsui.tests._tools import ( create_ui, @@ -13,6 +14,8 @@ reraise_exceptions, ToolkitName, ) +from traitsui.testing.tester import command, query +from traitsui.testing.tester.ui_tester import UITester class ButtonTextEdit(HasTraits): @@ -46,36 +49,25 @@ class ButtonTextEdit(HasTraits): ) -def get_button_text(button): - """ Return the button text given a button control """ - if is_wx(): - return button.GetLabel() - - elif is_qt(): - return button.text() - - @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) -class TestButtonEditor(unittest.TestCase): +class TestButtonEditor(unittest.TestCase, UnittestTools): def check_button_text_update(self, view): button_text_edit = ButtonTextEdit() - with reraise_exceptions(), \ - create_ui(button_text_edit, dict(view=view)) as ui: - - process_cascade_events() - editor, = ui.get_editors("play_button") - button = editor.control - - self.assertEqual(get_button_text(button), "I'm a play button") + tester = UITester() + with tester.create_ui(button_text_edit, dict(view=view)) as ui: + button = tester.find_by_name(ui, "play_button") + actual = button.inspect(query.DisplayedText()) + self.assertEqual(actual, "I'm a play button") button_text_edit.play_button_label = "New Label" - self.assertEqual(get_button_text(button), "New Label") + actual = button.inspect(query.DisplayedText()) + self.assertEqual(actual, "New Label") def test_styles(self): # simple smoke test of buttons button_text_edit = ButtonTextEdit() - with reraise_exceptions(), create_ui(button_text_edit): + with UITester().create_ui(button_text_edit): pass def test_simple_button_editor(self): @@ -84,6 +76,23 @@ def test_simple_button_editor(self): def test_custom_button_editor(self): self.check_button_text_update(custom_view) + def check_button_fired_event(self,view): + button_text_edit = ButtonTextEdit() + + tester = UITester() + with tester.create_ui(button_text_edit, dict(view=view)) as ui: + button = tester.find_by_name(ui, "play_button") + + with self.assertTraitChanges( + button_text_edit, "play_button", count=1): + button.perform(command.MouseClick()) + + def test_simple_button_editor_clicked(self): + self.check_button_fired_event(simple_view) + + def test_custom_button_editor_clicked(self): + self.check_button_fired_event(custom_view) + @requires_toolkit([ToolkitName.qt]) class TestButtonEditorValuesTrait(unittest.TestCase): @@ -106,7 +115,7 @@ def check_editor_values_trait_init_and_dispose(self, style): instance = ButtonTextEdit(values=["Item1", "Item2"]) view = self.get_view(style=style) with reraise_exceptions(): - with create_ui(instance, dict(view=view)): + with UITester().create_ui(instance, dict(view=view)): pass # It is okay to mutate trait after the GUI is disposed. From 57fcddab1bc3ea0c5926e43ed5671f743ae0521d Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 21 Aug 2020 07:32:22 -0500 Subject: [PATCH 11/60] addressing comments --- traitsui/testing/tester/default_registry.py | 4 +++- traitsui/testing/tester/ui_tester.py | 4 ++++ traitsui/testing/tester/ui_wrapper.py | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/traitsui/testing/tester/default_registry.py b/traitsui/testing/tester/default_registry.py index fc9b8e4c7..93fa02ec2 100644 --- a/traitsui/testing/tester/default_registry.py +++ b/traitsui/testing/tester/default_registry.py @@ -11,8 +11,11 @@ import importlib +from traits.etsconfig.api import ETSConfig + from traitsui.testing.tester.registry import TargetRegistry + def get_default_registry(): """ Creates a default registry for UITester that is toolkit specific. @@ -24,7 +27,6 @@ def get_default_registry(): """ # side-effect to determine current toolkit from pyface.toolkit import toolkit_object - from traits.etsconfig.api import ETSConfig if ETSConfig.toolkit == "null": return TargetRegistry() diff --git a/traitsui/testing/tester/ui_tester.py b/traitsui/testing/tester/ui_tester.py index 4ba6ba0dd..a09240955 100644 --- a/traitsui/testing/tester/ui_tester.py +++ b/traitsui/testing/tester/ui_tester.py @@ -130,6 +130,10 @@ class App(HasTraits): registries : list of TargetRegistry, optional Registries of interaction for different target, in the order of decreasing priority. A shallow copy will be made. + delay : int, optional + Time delay (in ms) in which actions by the tester are performed. Note + it is propogated through to created child wrappers. The delay allows + visual confirmation test code is working as desired. Defaults to 0. """ def __init__(self, registries=None, delay=0): diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index efdf34d8b..f4e8079e5 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -47,6 +47,10 @@ class UIWrapper: target : any An object on which further UI target can be searched for, or can be a leaf target that can be operated on. + delay : int, optional + Time delay (in ms) in which actions by the wrapper are performed. Note + it is propogated through to created child wrappers. The delay allows + visual confirmation test code is working as desired. Defaults to 0. """ def __init__(self, target, registries, delay=0): From 6266a8390565d25599e4bec3fb69fe46f221de0d Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 21 Aug 2020 08:20:20 -0500 Subject: [PATCH 12/60] updating helper functions and adding docstrings --- traitsui/testing/tester/qt4/helpers.py | 11 +++++++++++ traitsui/testing/tester/wx/default_registry.py | 2 +- traitsui/testing/tester/wx/helpers.py | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index b4ab3ee41..fde802294 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -14,6 +14,17 @@ from pyface.qt.QtTest import QTest def mouse_click_qwidget(wrapper, action): + """ Performs a mouce click on a Qt widget. + + Parameters + ---------- + wrapper : UIWrapper + The wrapper object wrapping the Qt widget. + interaction : instance of traitsui.testing.tester.command.MouseClick + interaction is unused here, but it is included so that the function + matches the expected format for a handler. Note this handler is + intended to be used with an interaction_class of a MouseClick. + """ QTest.mouseClick( wrapper.target, QtCore.Qt.LeftButton, diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index e812f5b9e..abcda4e84 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -50,7 +50,7 @@ def get_wobject_registry(): interaction_class=command.MouseClick, handler=lambda wrapper, _: ( helpers.mouse_click_button( - control=wrapper.target, delay=wrapper.delay, + wrapper=wrapper, interaction=command.MouseClick(), ) ) ) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 9a84db7f9..f2e5734c2 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -11,10 +11,22 @@ import wx -def mouse_click_button(control, delay): +def mouse_click_button(wrapper, interaction): + """ Performs a mouce click on a wx button. + + Parameters + ---------- + wrapper : UIWrapper + The wrapper object wrapping the wx button. + interaction : instance of traitsui.testing.tester.command.MouseClick + interaction is unused here, but it is included so that the function + matches the expected format for a handler. Note this handler is + intended to be used with an interaction_class of a MouseClick. + """ + control = wrapper.target if not control.IsEnabled(): return - wx.MilliSleep(delay) + wx.MilliSleep(wrapper.delay) click_event = wx.CommandEvent( wx.wxEVT_COMMAND_BUTTON_CLICKED, control.GetId() ) From fe4c597bd47a638365010a81d959416d76062b87 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 21 Aug 2020 13:12:37 -0500 Subject: [PATCH 13/60] adding __init__.py files --- traitsui/testing/tester/qt4/implementation/__init__.py | 0 traitsui/testing/tester/wx/implementation/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 traitsui/testing/tester/qt4/implementation/__init__.py create mode 100644 traitsui/testing/tester/wx/implementation/__init__.py diff --git a/traitsui/testing/tester/qt4/implementation/__init__.py b/traitsui/testing/tester/qt4/implementation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/traitsui/testing/tester/wx/implementation/__init__.py b/traitsui/testing/tester/wx/implementation/__init__.py new file mode 100644 index 000000000..e69de29bb From df8138067b88ec5c4db3b6b64c6e07129fd18311 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 21 Aug 2020 13:39:11 -0500 Subject: [PATCH 14/60] adding docstring for DefaultTarget locator class --- traitsui/testing/tester/locator.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index 9bcef0936..eeba646f6 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -37,4 +37,15 @@ def __init__(self, name): self.name = name class DefaultTarget: + """ A locator for locating a default target. In many cases, handlers may + not be registered for certain interactions on a given target. Instead + there may be a solver registered a locator_class of DefaultTarget and a + target_class of the original target. The registered solver will then + return some generic (toolkit specific) object, for which frequently used + handlers will be registered. If user tries to perform the intetractions + of these handlers on the original object, machinery is in place for the + UIWrapper to try and locate a DefaultTarget and then look for the needed + handler automatically, if it can not find a handler directly for the + original target and interaction. + """ pass From 98033754330bf2a833e47aebd266dc38230463a6 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 21 Aug 2020 15:10:29 -0500 Subject: [PATCH 15/60] first steps for KeySequence, KeyClick and DisplayedText for TextEditor --- traitsui/testing/tester/command.py | 31 +++++++++++ .../testing/tester/qt4/default_registry.py | 14 +++++ traitsui/testing/tester/qt4/helpers.py | 51 ++++++++++++++++++- .../tester/qt4/implementation/text_editor.py | 25 +++++++++ .../testing/tester/wx/default_registry.py | 30 ++++++++--- traitsui/testing/tester/wx/helpers.py | 27 +++++++++- .../tester/wx/implementation/text_editor.py | 22 ++++++++ 7 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 traitsui/testing/tester/qt4/implementation/text_editor.py create mode 100644 traitsui/testing/tester/wx/implementation/text_editor.py diff --git a/traitsui/testing/tester/command.py b/traitsui/testing/tester/command.py index bcf64c40b..a325444c5 100644 --- a/traitsui/testing/tester/command.py +++ b/traitsui/testing/tester/command.py @@ -26,3 +26,34 @@ class MouseClick: implementations should not raise an exception. """ 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 \ No newline at end of file diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 14275c478..4ea177a28 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -16,6 +16,7 @@ from traitsui.testing.tester.qt4 import helpers from traitsui.testing.tester.qt4.implementation import ( button_editor, + text_editor, ) def get_default_registry(): @@ -29,8 +30,12 @@ def get_default_registry(): """ registry = get_qobject_registry() + # ButtonEditor button_editor.register(registry) + # TextEditor + text_editor.register(registry) + return registry @@ -47,9 +52,12 @@ def get_qobject_registry(): registry = TargetRegistry() widget_classes = [ + QtGui.QTextEdit, QtGui.QPushButton, ] handlers = [ + (command.KeySequence, helpers.key_sequence_qwidget), + (command.KeyClick, helpers.key_press_qwidget), (command.MouseClick, helpers.mouse_click_qwidget), ] for widget_class in widget_classes: @@ -60,6 +68,12 @@ def get_qobject_registry(): handler=handler, ) + registry.register_handler( + target_class=QtGui.QTextEdit, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.toPlainText(), + ) + registry.register_handler( target_class=QtGui.QPushButton, interaction_class=query.DisplayedText, diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index fde802294..65efe1a63 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -9,11 +9,47 @@ # Thanks for using Enthought open source! # +from functools import reduce from pyface.qt import QtCore from pyface.qt.QtTest import QTest -def mouse_click_qwidget(wrapper, action): +from traitsui.testing.exceptions import Disabled +from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP + +def key_press(widget, key, delay=0): + if "-" in key: + *modifiers, key = key.split("-") + else: + modifiers = [] + + modifier_to_qt = { + "Ctrl": QtCore.Qt.ControlModifier, + "Alt": QtCore.Qt.AltModifier, + "Meta": QtCore.Qt.MetaModifier, + "Shift": QtCore.Qt.ShiftModifier, + } + qt_modifiers = [modifier_to_qt[modifier] for modifier in modifiers] + qt_modifier = reduce( + lambda x, y: x | y, qt_modifiers, QtCore.Qt.NoModifier + ) + + mapping = {name: event for event, name in _KEY_MAP.items()} + if key not in mapping: + raise ValueError( + "Unknown key {!r}. Expected one of these: {!r}".format( + key, sorted(mapping) + )) + QTest.keyClick( + widget, + mapping[key], + qt_modifier, + delay=delay, + ) + +## Generic Handlers ############################ + +def mouse_click_qwidget(wrapper, interaction): """ Performs a mouce click on a Qt widget. Parameters @@ -29,4 +65,15 @@ def mouse_click_qwidget(wrapper, action): wrapper.target, QtCore.Qt.LeftButton, delay=wrapper.delay, - ) \ No newline at end of file + ) + +def key_sequence_qwidget(wrapper, interaction): + if not wrapper.target.isEnabled(): + raise Disabled("{!r} is disabled.".format(wrapper.target)) + QTest.keyClicks(wrapper.target, interaction.sequence, delay=wrapper.delay) + + +def key_press_qwidget(wrapper, interaction): + if not wrapper.target.isEnabled(): + raise Disabled("{!r} is disabled.".format(wrapper.target)) + key_press(wrapper.target, interaction.key, delay=wrapper.delay) diff --git a/traitsui/testing/tester/qt4/implementation/text_editor.py b/traitsui/testing/tester/qt4/implementation/text_editor.py new file mode 100644 index 000000000..f7076e701 --- /dev/null +++ b/traitsui/testing/tester/qt4/implementation/text_editor.py @@ -0,0 +1,25 @@ +# 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.testing import locator +from traitsui.qt4.text_editor import CustomEditor, ReadonlyEditor, SimpleEditor + +def register(registry): + """ Register actions for the given registry. + + If there are any conflicts, an error will occur. + """ + for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: + registry.register_solver( + target_class=target_class, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.target.control, + ) + diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index abcda4e84..13d357277 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -11,11 +11,12 @@ import wx -from traitsui.testing.tester import command +from traitsui.testing.tester import command, query from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.wx import helpers from traitsui.testing.tester.wx.implementation import ( button_editor, + text_editor, ) @@ -30,7 +31,11 @@ def get_default_registry(): """ registry = get_wobject_registry() + # ButtonEditor button_editor.register(registry) + + # TextEditor + text_editor.register(registry) return registry @@ -45,14 +50,27 @@ def get_wobject_registry(): """ registry = TargetRegistry() + registry.register_handler( + target_class=wx.TextCtrl, + interaction_class=command.KeyClick, + handler=helpers.key_press_text_ctrl + ) + registry.register_handler( + target_class=wx.TextCtrl, + interaction_class=command.KeySequence, + handler=helpers.key_sequence_text_ctrl + ) + registry.register_handler( + target_class=wx.StaticText, + interaction_class=query.DisplayedText, + handler=lambda wrapper, action: ( + wrapper.target.GetLabel() + ), + ) registry.register_handler( target_class=wx.Button, interaction_class=command.MouseClick, - handler=lambda wrapper, _: ( - helpers.mouse_click_button( - wrapper=wrapper, interaction=command.MouseClick(), - ) - ) + handler=helpers.mouse_click_button ) return registry diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index f2e5734c2..3b847012a 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -30,4 +30,29 @@ def mouse_click_button(wrapper, interaction): click_event = wx.CommandEvent( wx.wxEVT_COMMAND_BUTTON_CLICKED, control.GetId() ) - control.ProcessEvent(click_event) \ No newline at end of file + control.ProcessEvent(click_event) + + +def key_press_text_ctrl(wrapper, interaction): + control = wrapper.target + if interaction.key == "Enter": + if not control.HasFocus(): + control.SetFocus() + wx.MilliSleep(wrapper.delay) + event = wx.CommandEvent(wx.EVT_TEXT_ENTER.typeId, control.GetId()) + control.ProcessEvent(event) + else: + raise ValueError("Only supported Enter key.") + + +def key_sequence_text_ctrl(wrapper, interaction): + control = wrapper.target + if not control.HasFocus(): + control.SetFocus() + for char in interaction.sequence: + wx.MilliSleep(wrapper.delay) + if char == "\b": + pos = control.GetInsertionPoint() + control.Remove(max(0, pos - 1), pos) + else: + control.AppendText(char) \ No newline at end of file diff --git a/traitsui/testing/tester/wx/implementation/text_editor.py b/traitsui/testing/tester/wx/implementation/text_editor.py new file mode 100644 index 000000000..e17f3e1e1 --- /dev/null +++ b/traitsui/testing/tester/wx/implementation/text_editor.py @@ -0,0 +1,22 @@ +# 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.text_editor import CustomEditor, ReadonlyEditor, SimpleEditor +from traitsui.testing import locator + + +def register(registry): + for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: + registry.register_solver( + target_class=target_class, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.target.control, + ) From 9077c08c9f774fec07201e550010274f0b6f85d2 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 21 Aug 2020 15:49:44 -0500 Subject: [PATCH 16/60] add QLineEdit, change key press names to click --- traitsui/testing/tester/qt4/default_registry.py | 9 ++++++++- traitsui/testing/tester/qt4/helpers.py | 6 +++--- .../testing/tester/qt4/implementation/text_editor.py | 1 - 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 4ea177a28..9ff23f897 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -52,12 +52,13 @@ def get_qobject_registry(): registry = TargetRegistry() widget_classes = [ + QtGui.QLineEdit, QtGui.QTextEdit, QtGui.QPushButton, ] handlers = [ (command.KeySequence, helpers.key_sequence_qwidget), - (command.KeyClick, helpers.key_press_qwidget), + (command.KeyClick, helpers.key_click_qwidget), (command.MouseClick, helpers.mouse_click_qwidget), ] for widget_class in widget_classes: @@ -68,6 +69,12 @@ def get_qobject_registry(): handler=handler, ) + registry.register_handler( + target_class=QtGui.QLineEdit, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.displayText(), + ) + registry.register_handler( target_class=QtGui.QTextEdit, interaction_class=query.DisplayedText, diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 65efe1a63..c0030eb68 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -17,7 +17,7 @@ from traitsui.testing.exceptions import Disabled from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP -def key_press(widget, key, delay=0): +def key_click(widget, key, delay=0): if "-" in key: *modifiers, key = key.split("-") else: @@ -73,7 +73,7 @@ def key_sequence_qwidget(wrapper, interaction): QTest.keyClicks(wrapper.target, interaction.sequence, delay=wrapper.delay) -def key_press_qwidget(wrapper, interaction): +def key_click_qwidget(wrapper, interaction): if not wrapper.target.isEnabled(): raise Disabled("{!r} is disabled.".format(wrapper.target)) - key_press(wrapper.target, interaction.key, delay=wrapper.delay) + key_click(wrapper.target, interaction.key, delay=wrapper.delay) diff --git a/traitsui/testing/tester/qt4/implementation/text_editor.py b/traitsui/testing/tester/qt4/implementation/text_editor.py index f7076e701..509d13223 100644 --- a/traitsui/testing/tester/qt4/implementation/text_editor.py +++ b/traitsui/testing/tester/qt4/implementation/text_editor.py @@ -22,4 +22,3 @@ def register(registry): locator_class=locator.DefaultTarget, solver=lambda wrapper, _: wrapper.target.control, ) - From 400d9d369943648aa9f8519d85d61af072b72d0f Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 07:45:43 -0500 Subject: [PATCH 17/60] docstring updates and simple name change --- traitsui/testing/tester/qt4/implementation/button_editor.py | 3 ++- traitsui/testing/tester/qt4/tests/test_default_registry.py | 2 +- traitsui/testing/tester/wx/implementation/button_editor.py | 5 +++++ traitsui/testing/tester/wx/tests/test_default_registry.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/traitsui/testing/tester/qt4/implementation/button_editor.py b/traitsui/testing/tester/qt4/implementation/button_editor.py index 4e1d695b5..c75dac474 100644 --- a/traitsui/testing/tester/qt4/implementation/button_editor.py +++ b/traitsui/testing/tester/qt4/implementation/button_editor.py @@ -13,7 +13,8 @@ def register(registry): - """ Register actions for the given registry. + """ Register solvers/handlers specific to qt Button Editors + for the given registry. If there are any conflicts, an error will occur. """ diff --git a/traitsui/testing/tester/qt4/tests/test_default_registry.py b/traitsui/testing/tester/qt4/tests/test_default_registry.py index 791a17be9..edbe6e9c7 100644 --- a/traitsui/testing/tester/qt4/tests/test_default_registry.py +++ b/traitsui/testing/tester/qt4/tests/test_default_registry.py @@ -30,7 +30,7 @@ @requires_toolkit([ToolkitName.qt]) -class TestInteractorAction(unittest.TestCase): +class TestInteractions(unittest.TestCase): def test_mouse_click(self): button = QtGui.QPushButton() diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py index 81654ad65..e9a799c9f 100644 --- a/traitsui/testing/tester/wx/implementation/button_editor.py +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -14,6 +14,11 @@ def register(registry): + """ Register solvers/handlers specific to wx Button Editors + for the given registry. + + If there are any conflicts, an error will occur. + """ for target_class in [SimpleEditor, CustomEditor]: registry.register_solver( target_class=target_class, diff --git a/traitsui/testing/tester/wx/tests/test_default_registry.py b/traitsui/testing/tester/wx/tests/test_default_registry.py index 534713d1a..31e86a31b 100644 --- a/traitsui/testing/tester/wx/tests/test_default_registry.py +++ b/traitsui/testing/tester/wx/tests/test_default_registry.py @@ -30,7 +30,7 @@ ) @requires_toolkit([ToolkitName.wx]) -class TestInteractorAction(unittest.TestCase): +class TestInteractions(unittest.TestCase): def setUp(self): self.app = wx.App() From 33f750f5e4031a94b8cf885fd70a73f13c7e6fc0 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 09:10:45 -0500 Subject: [PATCH 18/60] fixing typo --- traitsui/testing/tester/qt4/tests/test_default_registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/traitsui/testing/tester/qt4/tests/test_default_registry.py b/traitsui/testing/tester/qt4/tests/test_default_registry.py index edbe6e9c7..78b757314 100644 --- a/traitsui/testing/tester/qt4/tests/test_default_registry.py +++ b/traitsui/testing/tester/qt4/tests/test_default_registry.py @@ -37,7 +37,7 @@ def test_mouse_click(self): click_slot = mock.Mock() button.clicked.connect(click_slot) - wrapper = wrapper = UIWrapper( + wrapper = UIWrapper( target=button, registries=[default_registry.get_default_registry()], ) @@ -53,7 +53,7 @@ def test_mouse_click_disabled(self): click_slot = mock.Mock() button.clicked.connect(click_slot) - wrapper = wrapper = UIWrapper( + wrapper = UIWrapper( target=button, registries=[default_registry.get_default_registry()], ) From 51e5b458fa6b7e56dbfa8eb08a4a28e137bfaf48 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 11:23:57 -0500 Subject: [PATCH 19/60] adding tests for new UIWrapper DefaultTarget logic --- .../testing/tester/tests/test_ui_wrapper.py | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/traitsui/testing/tester/tests/test_ui_wrapper.py b/traitsui/testing/tester/tests/test_ui_wrapper.py index b54ffe2f7..4a735d3b3 100644 --- a/traitsui/testing/tester/tests/test_ui_wrapper.py +++ b/traitsui/testing/tester/tests/test_ui_wrapper.py @@ -23,6 +23,8 @@ InteractionNotSupported, LocationNotSupported, ) +from traitsui.testing.tester.registry import TargetRegistry +from traitsui.testing.tester import locator from traitsui.testing.tester.ui_wrapper import ( UIWrapper, ) @@ -297,3 +299,109 @@ def handler(wrapper, action): with self.assertRaises(ZeroDivisionError): wrapper.perform(None) + + +class Dummy1: + pass + + +class Dummy2: + pass + + +class DummyInteraction: + pass + + +class DummyInteraction2: + pass + + +class DummyInteraction3: + pass + + +class TestUIWrapperDefaultTarget(unittest.TestCase): + + def test_interaction_from_DefaultTarget(self): + registry = TargetRegistry() + registry.register_solver( + target_class=Dummy1, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: Dummy2() , + ) + + registry.register_handler( + target_class=Dummy2, + interaction_class=DummyInteraction, + handler=lambda wrapper, _: 1, + ) + + wrapper = UIWrapper( + target=Dummy1(), + registries=[registry], + ) + + self.assertEqual(wrapper.inspect(DummyInteraction()), 1) + + def test_supported_interactions_through_DefaultTarget(self): + registry = TargetRegistry() + registry.register_solver( + target_class=Dummy1, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: Dummy2() , + ) + registry.register_handler( + target_class=Dummy1, + interaction_class=DummyInteraction3, + handler=lambda wrapper, _: 1, + ) + registry.register_handler( + target_class=Dummy2, + interaction_class=DummyInteraction2, + handler=lambda wrapper, _: 1, + ) + + wrapper = UIWrapper( + target=Dummy1(), + registries=[registry], + ) + + with self.assertRaises(InteractionNotSupported) as exception_context: + wrapper.inspect(DummyInteraction()) + self.assertEqual(len(exception_context.exception.supported), 2) + + def test_location_not_supported_from_default_target_handler(self): + # Test we don't mask or misrepresent a LocationNotSupported error from + # a handler on the default target + registry = TargetRegistry() + registry.register_solver( + target_class=Dummy1, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: Dummy2() , + ) + + exception = LocationNotSupported( + target_class=None, + locator_class=None, + supported=[], + ) + + def bad_handler(wrapper, _): + # probably programming error + raise exception + + registry.register_handler( + target_class=Dummy2, + interaction_class=DummyInteraction, + handler=bad_handler, + ) + + wrapper = UIWrapper( + target=Dummy1(), + registries=[registry], + ) + with self.assertRaises(LocationNotSupported) as exception_context: + wrapper.inspect(DummyInteraction()) + + self.assertIs(exception_context.exception, exception) From 3149374a33b0eee32fd5dda21911c43f86077ae8 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 11:43:33 -0500 Subject: [PATCH 20/60] flake8 fixes --- traitsui/testing/tester/default_registry.py | 7 +++---- traitsui/testing/tester/locator.py | 3 ++- traitsui/testing/tester/qt4/default_registry.py | 5 ++--- traitsui/testing/tester/qt4/helpers.py | 5 +++-- .../tester/qt4/implementation/button_editor.py | 2 +- .../tester/qt4/tests/test_default_registry.py | 1 - .../testing/tester/tests/test_default_registry.py | 13 ++++++------- traitsui/testing/tester/tests/test_ui_tester.py | 3 +-- traitsui/testing/tester/tests/test_ui_wrapper.py | 15 ++++++++++----- traitsui/testing/tester/ui_tester.py | 2 +- traitsui/testing/tester/ui_wrapper.py | 10 ++++++---- traitsui/testing/tester/wx/default_registry.py | 7 ++++--- traitsui/testing/tester/wx/helpers.py | 5 +++-- .../tester/wx/implementation/button_editor.py | 4 +--- .../tester/wx/tests/test_default_registry.py | 14 +++++++------- 15 files changed, 50 insertions(+), 46 deletions(-) diff --git a/traitsui/testing/tester/default_registry.py b/traitsui/testing/tester/default_registry.py index 93fa02ec2..cbf4bc432 100644 --- a/traitsui/testing/tester/default_registry.py +++ b/traitsui/testing/tester/default_registry.py @@ -23,14 +23,13 @@ def get_default_registry(): ------- registry : TargetRegistry The default registry containing implementations for TraitsUI editors - that is toolkit specific. + that is toolkit specific. """ - # side-effect to determine current toolkit - from pyface.toolkit import toolkit_object if ETSConfig.toolkit == "null": return TargetRegistry() else: toolkit = {'wx': 'wx', 'qt4': 'qt4', 'qt': 'qt4'}[ETSConfig.toolkit] - module = importlib.import_module(".default_registry", "traitsui.testing.tester." + toolkit) + module = importlib.import_module(".default_registry", + "traitsui.testing.tester." + toolkit) return module.get_default_registry() diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index eeba646f6..5d4a6a853 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -36,6 +36,7 @@ class TargetByName: def __init__(self, name): self.name = name + class DefaultTarget: """ A locator for locating a default target. In many cases, handlers may not be registered for certain interactions on a given target. Instead @@ -46,6 +47,6 @@ class DefaultTarget: of these handlers on the original object, machinery is in place for the UIWrapper to try and locate a DefaultTarget and then look for the needed handler automatically, if it can not find a handler directly for the - original target and interaction. + original target and interaction. """ pass diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 14275c478..a0d0f1e29 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -18,6 +18,7 @@ button_editor, ) + def get_default_registry(): """ Creates a default registry for UITester that is qt specific. @@ -25,13 +26,12 @@ def get_default_registry(): ------- registry : TargetRegistry The default registry containing implementations for TraitsUI editors - that is qt specific. + that is qt specific. """ registry = get_qobject_registry() button_editor.register(registry) - return registry @@ -67,4 +67,3 @@ def get_qobject_registry(): ) return registry - diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index fde802294..45cca31ac 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -13,6 +13,7 @@ from pyface.qt import QtCore from pyface.qt.QtTest import QTest + def mouse_click_qwidget(wrapper, action): """ Performs a mouce click on a Qt widget. @@ -23,10 +24,10 @@ def mouse_click_qwidget(wrapper, action): interaction : instance of traitsui.testing.tester.command.MouseClick interaction is unused here, but it is included so that the function matches the expected format for a handler. Note this handler is - intended to be used with an interaction_class of a MouseClick. + intended to be used with an interaction_class of a MouseClick. """ QTest.mouseClick( wrapper.target, QtCore.Qt.LeftButton, delay=wrapper.delay, - ) \ No newline at end of file + ) diff --git a/traitsui/testing/tester/qt4/implementation/button_editor.py b/traitsui/testing/tester/qt4/implementation/button_editor.py index c75dac474..626e14111 100644 --- a/traitsui/testing/tester/qt4/implementation/button_editor.py +++ b/traitsui/testing/tester/qt4/implementation/button_editor.py @@ -8,7 +8,7 @@ # # Thanks for using Enthought open source! # -from traitsui.testing.tester import locator, query +from traitsui.testing.tester import locator from traitsui.qt4.button_editor import CustomEditor, SimpleEditor diff --git a/traitsui/testing/tester/qt4/tests/test_default_registry.py b/traitsui/testing/tester/qt4/tests/test_default_registry.py index 78b757314..2357e2a23 100644 --- a/traitsui/testing/tester/qt4/tests/test_default_registry.py +++ b/traitsui/testing/tester/qt4/tests/test_default_registry.py @@ -12,7 +12,6 @@ import unittest from unittest import mock -#from pyface.gui import GUI from traitsui.tests._tools import ( is_qt, requires_toolkit, diff --git a/traitsui/testing/tester/tests/test_default_registry.py b/traitsui/testing/tester/tests/test_default_registry.py index 4f41c8260..4c580ee58 100644 --- a/traitsui/testing/tester/tests/test_default_registry.py +++ b/traitsui/testing/tester/tests/test_default_registry.py @@ -23,11 +23,10 @@ def test_load_default_registries(self): self.assertIsInstance(registry, TargetRegistry) if not is_null(): self.assertGreaterEqual( - len(registry._interaction_registry._target_to_key_to_value), - 1, - ) + len(registry._interaction_registry._target_to_key_to_value), + 1, + ) self.assertGreaterEqual( - len(registry._location_registry._target_to_key_to_value), - 1, - ) - + len(registry._location_registry._target_to_key_to_value), + 1, + ) diff --git a/traitsui/testing/tester/tests/test_ui_tester.py b/traitsui/testing/tester/tests/test_ui_tester.py index 700aab5e1..9e8811747 100644 --- a/traitsui/testing/tester/tests/test_ui_tester.py +++ b/traitsui/testing/tester/tests/test_ui_tester.py @@ -105,6 +105,5 @@ def test_delay_persisted(self): tester = UITester(delay=.01) view = View(Item("submit_button")) with tester.create_ui(Order(), dict(view=view)) as ui: - wrapped = tester.find_by_name(ui,"submit_button") + wrapped = tester.find_by_name(ui, "submit_button") self.assertEqual(wrapped.delay, .01) - diff --git a/traitsui/testing/tester/tests/test_ui_wrapper.py b/traitsui/testing/tester/tests/test_ui_wrapper.py index 4a735d3b3..43b0fbf3d 100644 --- a/traitsui/testing/tester/tests/test_ui_wrapper.py +++ b/traitsui/testing/tester/tests/test_ui_wrapper.py @@ -119,12 +119,14 @@ def test_registry_all_declined(self): # supported. class EmptyRegistry1: + def get_handler(self, target_class, interaction_class): raise InteractionNotSupported( target_class=None, interaction_class=None, supported=[int], ) + def get_solver(self, target_class, interaction_class): raise LocationNotSupported( target_class=None, @@ -133,12 +135,14 @@ def get_solver(self, target_class, interaction_class): ) class EmptyRegistry2: + def get_handler(self, target_class, interaction_class): raise InteractionNotSupported( target_class=None, interaction_class=None, supported=[float], ) + def get_solver(self, target_class, interaction_class): raise LocationNotSupported( target_class=None, @@ -321,6 +325,7 @@ class DummyInteraction3: pass +@requires_toolkit([ToolkitName.qt, ToolkitName.wx]) class TestUIWrapperDefaultTarget(unittest.TestCase): def test_interaction_from_DefaultTarget(self): @@ -328,7 +333,7 @@ def test_interaction_from_DefaultTarget(self): registry.register_solver( target_class=Dummy1, locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: Dummy2() , + solver=lambda wrapper, _: Dummy2(), ) registry.register_handler( @@ -349,7 +354,7 @@ def test_supported_interactions_through_DefaultTarget(self): registry.register_solver( target_class=Dummy1, locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: Dummy2() , + solver=lambda wrapper, _: Dummy2(), ) registry.register_handler( target_class=Dummy1, @@ -378,9 +383,9 @@ def test_location_not_supported_from_default_target_handler(self): registry.register_solver( target_class=Dummy1, locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: Dummy2() , + solver=lambda wrapper, _: Dummy2(), ) - + exception = LocationNotSupported( target_class=None, locator_class=None, @@ -403,5 +408,5 @@ def bad_handler(wrapper, _): ) with self.assertRaises(LocationNotSupported) as exception_context: wrapper.inspect(DummyInteraction()) - + self.assertIs(exception_context.exception, exception) diff --git a/traitsui/testing/tester/ui_tester.py b/traitsui/testing/tester/ui_tester.py index a09240955..648131b27 100644 --- a/traitsui/testing/tester/ui_tester.py +++ b/traitsui/testing/tester/ui_tester.py @@ -133,7 +133,7 @@ class App(HasTraits): delay : int, optional Time delay (in ms) in which actions by the tester are performed. Note it is propogated through to created child wrappers. The delay allows - visual confirmation test code is working as desired. Defaults to 0. + visual confirmation test code is working as desired. Defaults to 0. """ def __init__(self, registries=None, delay=0): diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index 09199b99a..cbb34cf36 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -50,7 +50,7 @@ class UIWrapper: delay : int, optional Time delay (in ms) in which actions by the wrapper are performed. Note it is propogated through to created child wrappers. The delay allows - visual confirmation test code is working as desired. Defaults to 0. + visual confirmation test code is working as desired. Defaults to 0. """ def __init__(self, target, registries, delay=0): @@ -163,10 +163,12 @@ def _perform_or_inspect(self, interaction): try: # there may not be a handler registered for this interaction on - # the original target. Instead, it may be registered for DefaultTarget + # the original target. Instead, it may be registered for + # DefaultTarget default_target = self.locate(locator.DefaultTarget()) - # If there we can't solve from the current target to DefaultTarget this - # interaction isn't supported and we can raise appropraite exception below + # If there we can't solve from the current target to DefaultTarget this + # interaction isn't supported and we can raise appropraite exception + # below except LocationNotSupported: pass else: diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index abcda4e84..2ad6d8e4a 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -9,7 +9,7 @@ # Thanks for using Enthought open source! # -import wx +import wx from traitsui.testing.tester import command from traitsui.testing.tester.registry import TargetRegistry @@ -26,14 +26,15 @@ def get_default_registry(): ------- registry : TargetRegistry The default registry containing implementations for TraitsUI editors - that is wx specific. + that is wx specific. """ registry = get_wobject_registry() button_editor.register(registry) - + return registry + def get_wobject_registry(): """ Creates a generic registry for handling/solving wx objects. (i.e. this registry is independent of TraitsUI) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index f2e5734c2..a1ff756e3 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -11,6 +11,7 @@ import wx + def mouse_click_button(wrapper, interaction): """ Performs a mouce click on a wx button. @@ -21,7 +22,7 @@ def mouse_click_button(wrapper, interaction): interaction : instance of traitsui.testing.tester.command.MouseClick interaction is unused here, but it is included so that the function matches the expected format for a handler. Note this handler is - intended to be used with an interaction_class of a MouseClick. + intended to be used with an interaction_class of a MouseClick. """ control = wrapper.target if not control.IsEnabled(): @@ -30,4 +31,4 @@ def mouse_click_button(wrapper, interaction): click_event = wx.CommandEvent( wx.wxEVT_COMMAND_BUTTON_CLICKED, control.GetId() ) - control.ProcessEvent(click_event) \ No newline at end of file + control.ProcessEvent(click_event) diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py index e9a799c9f..24e17e665 100644 --- a/traitsui/testing/tester/wx/implementation/button_editor.py +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -9,8 +9,7 @@ # Thanks for using Enthought open source! # from traitsui.wx.button_editor import SimpleEditor, CustomEditor -from traitsui.testing.tester import locator, query -from traitsui.testing.tester.wx import helpers +from traitsui.testing.tester import locator def register(registry): @@ -25,4 +24,3 @@ def register(registry): locator_class=locator.DefaultTarget, solver=lambda wrapper, _: wrapper.target.control, ) - diff --git a/traitsui/testing/tester/wx/tests/test_default_registry.py b/traitsui/testing/tester/wx/tests/test_default_registry.py index 31e86a31b..c31725354 100644 --- a/traitsui/testing/tester/wx/tests/test_default_registry.py +++ b/traitsui/testing/tester/wx/tests/test_default_registry.py @@ -12,22 +12,22 @@ import unittest from unittest import mock -from pyface.api import GUI - from traitsui.testing.tester import command from traitsui.testing.tester.ui_wrapper import UIWrapper +from traitsui.tests._tools import ( + is_wx, + requires_toolkit, + ToolkitName, +) + try: import wx from traitsui.testing.tester.wx import default_registry except ImportError: if is_wx(): raise -from traitsui.tests._tools import ( - is_wx, - requires_toolkit, - ToolkitName, -) + @requires_toolkit([ToolkitName.wx]) class TestInteractions(unittest.TestCase): From c43c88ab12e8ff3fa28f40bb276e5b92f3c31f5b Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 11:56:04 -0500 Subject: [PATCH 21/60] rename wobject wx_object --- traitsui/testing/tester/wx/default_registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 2ad6d8e4a..72cb446b1 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -28,14 +28,14 @@ def get_default_registry(): The default registry containing implementations for TraitsUI editors that is wx specific. """ - registry = get_wobject_registry() + registry = get_wx_object_registry() button_editor.register(registry) return registry -def get_wobject_registry(): +def get_wx_object_registry(): """ Creates a generic registry for handling/solving wx objects. (i.e. this registry is independent of TraitsUI) From e4446281a47a7cf915e81f28dc06ac91d5611311 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 13:50:46 -0500 Subject: [PATCH 22/60] fixing wx test failures --- .../testing/tester/wx/default_registry.py | 28 +++++++++++++++---- traitsui/testing/tester/wx/helpers.py | 28 +++++++++++++++++++ .../tester/wx/implementation/button_editor.py | 6 ++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 72cb446b1..7ce607c11 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -11,7 +11,9 @@ import wx -from traitsui.testing.tester import command +from pyface.ui.wx.image_button import ImageButton + +from traitsui.testing.tester import command, query from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.wx import helpers from traitsui.testing.tester.wx.implementation import ( @@ -49,11 +51,25 @@ def get_wx_object_registry(): registry.register_handler( target_class=wx.Button, interaction_class=command.MouseClick, - handler=lambda wrapper, _: ( - helpers.mouse_click_button( - wrapper=wrapper, interaction=command.MouseClick(), - ) - ) + handler=helpers.mouse_click_button + ) + + registry.register_handler( + target_class=wx.Button, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.GetLabel() + ) + + registry.register_handler( + target_class=wx.Window, + interaction_class=command.MouseClick, + handler=helpers.mouse_click_ImageButton + ) + + registry.register_handler( + target_class=wx.Window, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.GetLabel() ) return registry diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index a1ff756e3..6103199ae 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -32,3 +32,31 @@ def mouse_click_button(wrapper, interaction): wx.wxEVT_COMMAND_BUTTON_CLICKED, control.GetId() ) control.ProcessEvent(click_event) + + +def mouse_click_ImageButton(wrapper, interaction): + """ Performs a mouce click on an pyface.ui.wx.ImageButton object. + + Parameters + ---------- + wrapper : UIWrapper + The wrapper object wrapping the ImageButton. + interaction : instance of traitsui.testing.tester.command.MouseClick + interaction is unused here, but it is included so that the function + matches the expected format for a handler. Note this handler is + intended to be used with an interaction_class of a MouseClick. + """ + + control = wrapper.target + if not control.IsEnabled(): + return + wx.MilliSleep(wrapper.delay) + + left_down_event = wx.MouseEvent( + wx.wxEVT_LEFT_DOWN + ) + left_up_event = wx.MouseEvent( + wx.wxEVT_LEFT_UP + ) + control.ProcessEvent(left_down_event) + control.ProcessEvent(left_up_event) diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py index 24e17e665..297a44b01 100644 --- a/traitsui/testing/tester/wx/implementation/button_editor.py +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -24,3 +24,9 @@ def register(registry): locator_class=locator.DefaultTarget, solver=lambda wrapper, _: wrapper.target.control, ) + + """registry.register_solver( + target_class=CustomEditor, + locator_class=locator.DefaultTarget, + solver=lambda wrapper, _: wrapper.target._control, + )""" \ No newline at end of file From d4d8012cdfbdb6c90a7cd3fae32877f36f1d4617 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 13:52:54 -0500 Subject: [PATCH 23/60] more flake8 fixes --- traitsui/testing/tester/wx/default_registry.py | 2 -- .../testing/tester/wx/implementation/button_editor.py | 6 ------ traitsui/tests/editors/test_button_editor.py | 8 +------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 7ce607c11..f358c48bb 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -11,8 +11,6 @@ import wx -from pyface.ui.wx.image_button import ImageButton - from traitsui.testing.tester import command, query from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.wx import helpers diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py index 297a44b01..24e17e665 100644 --- a/traitsui/testing/tester/wx/implementation/button_editor.py +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -24,9 +24,3 @@ def register(registry): locator_class=locator.DefaultTarget, solver=lambda wrapper, _: wrapper.target.control, ) - - """registry.register_solver( - target_class=CustomEditor, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: wrapper.target._control, - )""" \ No newline at end of file diff --git a/traitsui/tests/editors/test_button_editor.py b/traitsui/tests/editors/test_button_editor.py index b644f071b..439fffdd9 100644 --- a/traitsui/tests/editors/test_button_editor.py +++ b/traitsui/tests/editors/test_button_editor.py @@ -1,15 +1,9 @@ import unittest -from pyface.gui import GUI - from traits.api import Button, HasTraits, List, Str from traits.testing.api import UnittestTools from traitsui.api import ButtonEditor, Item, UItem, View from traitsui.tests._tools import ( - create_ui, - is_qt, - is_wx, - process_cascade_events, requires_toolkit, reraise_exceptions, ToolkitName, @@ -76,7 +70,7 @@ def test_simple_button_editor(self): def test_custom_button_editor(self): self.check_button_text_update(custom_view) - def check_button_fired_event(self,view): + def check_button_fired_event(self, view): button_text_edit = ButtonTextEdit() tester = UITester() From 848d513d24a3997de409c3667c89d0987810de1b Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 13:59:05 -0500 Subject: [PATCH 24/60] making suggested changes from comments --- traitsui/testing/tester/locator.py | 6 +++++- traitsui/testing/tester/qt4/implementation/button_editor.py | 4 ++++ traitsui/testing/tester/wx/implementation/button_editor.py | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index 5d4a6a853..ffdb61cf2 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -38,7 +38,7 @@ def __init__(self, name): class DefaultTarget: - """ A locator for locating a default target. In many cases, handlers may + """ A locator for locating a default target. In some cases, handlers may not be registered for certain interactions on a given target. Instead there may be a solver registered a locator_class of DefaultTarget and a target_class of the original target. The registered solver will then @@ -48,5 +48,9 @@ class DefaultTarget: UIWrapper to try and locate a DefaultTarget and then look for the needed handler automatically, if it can not find a handler directly for the original target and interaction. + + Note: This object will likely be used by developers implementing solvers/ + handlers. For those just using UITester, this object should work its magic + under-the-hood, and not be needed directly. """ pass diff --git a/traitsui/testing/tester/qt4/implementation/button_editor.py b/traitsui/testing/tester/qt4/implementation/button_editor.py index 626e14111..255458a6d 100644 --- a/traitsui/testing/tester/qt4/implementation/button_editor.py +++ b/traitsui/testing/tester/qt4/implementation/button_editor.py @@ -17,6 +17,10 @@ def register(registry): for the given registry. If there are any conflicts, an error will occur. + + Parameters + ---------- + registry : TargetRegistry """ for target_class in [SimpleEditor, CustomEditor]: registry.register_solver( diff --git a/traitsui/testing/tester/wx/implementation/button_editor.py b/traitsui/testing/tester/wx/implementation/button_editor.py index 24e17e665..52df78945 100644 --- a/traitsui/testing/tester/wx/implementation/button_editor.py +++ b/traitsui/testing/tester/wx/implementation/button_editor.py @@ -17,6 +17,10 @@ def register(registry): for the given registry. If there are any conflicts, an error will occur. + + Parameters + ---------- + registry : TargetRegistry """ for target_class in [SimpleEditor, CustomEditor]: registry.register_solver( From 975381674216e7a31c7875173df7f0f788b49d50 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 14:48:43 -0500 Subject: [PATCH 25/60] pull new DefaultTarget logic out into its own private method, and add more detailed documentation for it --- traitsui/testing/tester/ui_wrapper.py | 60 ++++++++++++++++++++------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index cbb34cf36..3eb84bce1 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -161,31 +161,61 @@ def _perform_or_inspect(self, interaction): with _event_processed(): return handler(self, interaction) + try_DefaultTarget = self._try_interaction_via_DefaultTarget(supported, interaction) + if try_DefaultTarget: + return try_DefaultTarget + + raise InteractionNotSupported( + target_class=self.target.__class__, + interaction_class=interaction.__class__, + supported=supported, + ) + + def _try_interaction_via_DefaultTarget(self, supported, interaction): + """ There may not be a handler registered for an interaction on + the original target (which is represented by self). Instead, it + may be registered for DefaultTarget. To address this possibility, + this method attempts to resolve from the current target to a + DefaultTarget, and then sees if there is an appropriate handler + that can perform the given interaction on DefaultTarget instead. If + so, that handler will ultimately be called and this method returns + the result. If not, any interactions for which there are handlers + registered for DefaultTarget will be added to the list of supported + interactiontypes. + + Parameters + ---------- + supported : List + A list of the interactions currently known to be supported for + the wrapper object. If no handler is found for the given + interaction with DefaultTarget as the target_class, any handlers + supported for DefaultTarget are added to the list. + interaction : instance of interaction type + An object defining the interaction. + + Returns + ------- + Any + The output of the call to the appropriate handler (if found) + None + If no appropriate handler is found, the method returns None, and + simply adds to the supported list if necessary. + """ try: - # there may not be a handler registered for this interaction on - # the original target. Instead, it may be registered for - # DefaultTarget default_target = self.locate(locator.DefaultTarget()) - # If there we can't solve from the current target to DefaultTarget this - # interaction isn't supported and we can raise appropraite exception - # below + # If we can't solve from the current target to DefaultTarget this + # interaction isn't supported, and there is nothing to add to supported except LocationNotSupported: pass else: - # if we can locate a DefaultTarget from the current target, try to - # perform the interactation on it instead + # if we can locate a DefaultTarget try to perform the + # interactation on it instead try: return default_target._perform_or_inspect(interaction) - # if we can't add to the list of things which are supported for - # the DefaultTarget + # if we can't add to supported except InteractionNotSupported as e: supported.extend(e.supported) - raise InteractionNotSupported( - target_class=self.target.__class__, - interaction_class=interaction.__class__, - supported=supported, - ) def _get_next_target(self, location): """ Return the next UI target from the given location. From db80b28f2f09f8135e6c546af7e70b6fa017f431 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 15:32:53 -0500 Subject: [PATCH 26/60] fixing failures from last commit --- traitsui/testing/tester/ui_wrapper.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index 3eb84bce1..95996e40c 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -162,7 +162,7 @@ def _perform_or_inspect(self, interaction): return handler(self, interaction) try_DefaultTarget = self._try_interaction_via_DefaultTarget(supported, interaction) - if try_DefaultTarget: + if try_DefaultTarget != -1: return try_DefaultTarget raise InteractionNotSupported( @@ -173,15 +173,15 @@ def _perform_or_inspect(self, interaction): def _try_interaction_via_DefaultTarget(self, supported, interaction): """ There may not be a handler registered for an interaction on - the original target (which is represented by self). Instead, it - may be registered for DefaultTarget. To address this possibility, - this method attempts to resolve from the current target to a - DefaultTarget, and then sees if there is an appropriate handler - that can perform the given interaction on DefaultTarget instead. If - so, that handler will ultimately be called and this method returns - the result. If not, any interactions for which there are handlers - registered for DefaultTarget will be added to the list of supported - interactiontypes. + the original target (which is represented by self). Instead, it + may be registered for DefaultTarget. To address this possibility, + this method attempts to resolve from the current target to a + DefaultTarget, and then sees if there is an appropriate handler + that can perform the given interaction on DefaultTarget instead. If + so, that handler will ultimately be called and this method returns + the result. If not, any interactions for which there are handlers + registered for DefaultTarget will be added to the list of supported + interactiontypes. Parameters ---------- @@ -215,6 +215,7 @@ def _try_interaction_via_DefaultTarget(self, supported, interaction): # if we can't add to supported except InteractionNotSupported as e: supported.extend(e.supported) + return -1 def _get_next_target(self, location): From bc0e7f19a94cdd73cc28ec8da2be94a15e2fd12f Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 15:53:40 -0500 Subject: [PATCH 27/60] first steps towards updating tests --- traitsui/testing/tester/exceptions.py | 9 ++ traitsui/testing/tester/qt4/helpers.py | 2 +- .../tester/qt4/implementation/text_editor.py | 2 +- .../tester/qt4/tests/test_default_registry.py | 71 ++++++++- traitsui/tests/editors/test_text_editor.py | 149 ++++++------------ 5 files changed, 132 insertions(+), 101 deletions(-) diff --git a/traitsui/testing/tester/exceptions.py b/traitsui/testing/tester/exceptions.py index 8cc4c79fe..6f1ba8269 100644 --- a/traitsui/testing/tester/exceptions.py +++ b/traitsui/testing/tester/exceptions.py @@ -15,6 +15,15 @@ class TesterError(Exception): pass +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 + class InteractionNotSupported(TesterError): """ Raised when an interaction is not supported by a wrapper. diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index c0030eb68..c6fa7f02e 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -14,7 +14,7 @@ from pyface.qt import QtCore from pyface.qt.QtTest import QTest -from traitsui.testing.exceptions import Disabled +from traitsui.testing.tester.exceptions import Disabled from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP def key_click(widget, key, delay=0): diff --git a/traitsui/testing/tester/qt4/implementation/text_editor.py b/traitsui/testing/tester/qt4/implementation/text_editor.py index 509d13223..ceb43db4f 100644 --- a/traitsui/testing/tester/qt4/implementation/text_editor.py +++ b/traitsui/testing/tester/qt4/implementation/text_editor.py @@ -8,7 +8,7 @@ # # Thanks for using Enthought open source! # -from traitsui.testing import locator +from traitsui.testing.tester import locator from traitsui.qt4.text_editor import CustomEditor, ReadonlyEditor, SimpleEditor def register(registry): diff --git a/traitsui/testing/tester/qt4/tests/test_default_registry.py b/traitsui/testing/tester/qt4/tests/test_default_registry.py index 791a17be9..e9905241f 100644 --- a/traitsui/testing/tester/qt4/tests/test_default_registry.py +++ b/traitsui/testing/tester/qt4/tests/test_default_registry.py @@ -13,12 +13,14 @@ from unittest import mock #from pyface.gui import GUI +from traits.testing.api import UnittestTools from traitsui.tests._tools import ( is_qt, requires_toolkit, ToolkitName, ) -from traitsui.testing.tester import command +from traitsui.testing.tester import command, query +from traitsui.testing.tester.exceptions import Disabled from traitsui.testing.tester.ui_wrapper import UIWrapper try: @@ -30,7 +32,7 @@ @requires_toolkit([ToolkitName.qt]) -class TestInteractorAction(unittest.TestCase): +class TestInteractorAction(unittest.TestCase, UnittestTools): def test_mouse_click(self): button = QtGui.QPushButton() @@ -65,3 +67,68 @@ def test_mouse_click_disabled(self): # then self.assertEqual(click_slot.call_count, 0) + + + def test_key_sequence(self): + textbox = QtGui.QLineEdit() + change_slot = mock.Mock() + textbox.textEdited.connect(change_slot) + + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + + # when + wrapper.perform(command.KeySequence("abc")) + # then + self.assertEqual(textbox.text(), "abc") + # each keystroke fires a signal + self.assertEqual(change_slot.call_count, 3) + + + def test_key_sequence_disabled(self): + textbox = QtGui.QLineEdit() + textbox.setEnabled(False) + + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + + # then + # this will fail, because one should not be allowed to set + # cursor on the widget to type anything + with self.assertRaises(Disabled): + wrapper.perform(command.KeySequence("abc")) + + + def test_key_click(self): + textbox = QtGui.QLineEdit() + change_slot = mock.Mock() + textbox.editingFinished.connect(change_slot) + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + # sanity check + wrapper.perform(command.KeySequence("abc")) + self.assertEqual(change_slot.call_count, 0) + + wrapper.perform(command.KeyClick("Enter")) + self.assertEqual(change_slot.call_count, 1) + + + def test_key_click_disabled(self): + textbox = QtGui.QLineEdit() + textbox.setEnabled(False) + change_slot = mock.Mock() + textbox.editingFinished.connect(change_slot) + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + + with self.assertRaises(Disabled): + wrapper.perform(command.KeyClick("Enter")) + self.assertEqual(change_slot.call_count, 0) diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index 92d217c07..b49fe23c9 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -18,16 +18,17 @@ from traits.api import ( HasTraits, Str, + pop_exception_handler, + push_exception_handler, ) +from traits.testing.api import UnittestTools from traitsui.api import TextEditor, View, Item +from traitsui.testing.tester.ui_tester import UITester +from traitsui.testing.tester import command, query from traitsui.tests._tools import ( - create_ui, GuiTestAssistant, - is_qt, no_gui_test_assistant, - process_cascade_events, requires_toolkit, - reraise_exceptions, ToolkitName, ) @@ -54,55 +55,10 @@ def get_view(style, auto_set): ) -def get_text(editor): - """ Return the text from the widget for checking. - """ - if is_qt(): - return editor.control.text() - else: - raise unittest.SkipTest("Not implemented for the current toolkit.") - - -def set_text(editor, text): - """ Imitate user changing the text on the text box to a new value. Note - that this is equivalent to "clear and insert", which excludes confirmation - via pressing a return key or causing the widget to lose focus. - """ - - if is_qt(): - from pyface.qt import QtGui - if editor.base_style == QtGui.QLineEdit: - editor.control.clear() - editor.control.insert(text) - editor.control.textEdited.emit(text) - else: - editor.control.setText(text) - editor.control.textChanged.emit() - else: - raise unittest.SkipTest("Not implemented for the current toolkit.") - - -def key_press_return(editor): - """ Imitate user pressing the return key. - """ - if is_qt(): - from pyface.qt import QtGui - - # ideally we should fire keyPressEvent, but the editor does not - # bind to this event. Pressing return key will fire editingFinished - # event on a QLineEdit - if editor.base_style == QtGui.QLineEdit: - editor.control.editingFinished.emit() - else: - editor.control.append("") - else: - raise unittest.SkipTest("Not implemented for the current toolkit.") - - # Skips tests if the backend is not either qt4 or qt5 @requires_toolkit([ToolkitName.qt]) @unittest.skipIf(no_gui_test_assistant, "No GuiTestAssistant") -class TestTextEditorQt(GuiTestAssistant, unittest.TestCase): +class TestTextEditorQt(GuiTestAssistant, UnittestTools, unittest.TestCase): """ Test on TextEditor with Qt backend.""" def test_text_editor_placeholder_text(self): @@ -111,13 +67,16 @@ def test_text_editor_placeholder_text(self): placeholder="Enter name", ) view = View(Item(name="name", editor=editor)) - with create_ui(foo, dict(view=view)) as ui: + + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: name_editor, = ui.get_editors("name") self.assertEqual( name_editor.control.placeholderText(), "Enter name", ) + def test_text_editor_placeholder_text_and_readonly(self): # Placeholder can be set independently of read_only flag foo = Foo() @@ -126,7 +85,8 @@ def test_text_editor_placeholder_text_and_readonly(self): read_only=True, ) view = View(Item(name="name", editor=editor)) - with create_ui(foo, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: name_editor, = ui.get_editors("name") self.assertEqual( name_editor.control.placeholderText(), @@ -135,7 +95,8 @@ def test_text_editor_placeholder_text_and_readonly(self): def test_text_editor_default_view(self): foo = Foo() - with create_ui(foo) as ui: + tester = UITester() + with tester.create_ui(foo) as ui: name_editor, = ui.get_editors("name") self.assertEqual( name_editor.control.placeholderText(), @@ -150,7 +111,8 @@ def test_text_editor_custom_style_placeholder(self): style="custom", editor=TextEditor(placeholder="Enter name"), )) - with create_ui(foo, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: name_editor, = ui.get_editors("name") try: placeholder = name_editor.control.placeholderText() @@ -164,17 +126,20 @@ def test_text_editor_custom_style_placeholder(self): # We should be able to run this test case against wx. # Not running them now to avoid test interaction. See enthought/traitsui#752 @requires_toolkit([ToolkitName.qt]) -class TestTextEditor(unittest.TestCase): +class TestTextEditor(unittest.TestCase, UnittestTools): """ Tests that can be run with any toolkit as long as there is an implementation for simulating user interactions. """ + def setUp(self): + push_exception_handler(reraise_exceptions=True) + self.addCleanup(pop_exception_handler) + def check_editor_init_and_dispose(self, style, auto_set): # Smoke test to test setup and tear down of an editor. foo = Foo() view = get_view(style=style, auto_set=auto_set) - with reraise_exceptions(), \ - create_ui(foo, dict(view=view)): + with UITester().create_ui(foo, dict(view=view)) as ui: pass def test_simple_editor_init_and_dispose(self): @@ -196,60 +161,46 @@ def test_custom_editor_init_and_dispose_no_auto_set(self): def test_simple_auto_set_update_text(self): foo = Foo() view = get_view(style="simple", auto_set=True) - with reraise_exceptions(), \ - create_ui(foo, dict(view=view)) as ui: - editor, = ui.get_editors("name") - set_text(editor, "NEW") - process_cascade_events() - + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + with self.assertTraitChanges(foo, "name", count=3): + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) self.assertEqual(foo.name, "NEW") + def test_simple_auto_set_false_do_not_update(self): foo = Foo(name="") view = get_view(style="simple", auto_set=False) - with reraise_exceptions(), \ - create_ui(foo, dict(view=view)) as ui: - editor, = ui.get_editors("name") - - set_text(editor, "NEW") - process_cascade_events() - + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) self.assertEqual(foo.name, "") - - key_press_return(editor) - process_cascade_events() - + tester.find_by_name(ui, "name").perform(command.KeyClick("Enter")) self.assertEqual(foo.name, "NEW") + def test_custom_auto_set_true_update_text(self): # the auto_set flag is disregard for custom editor. foo = Foo() view = get_view(auto_set=True, style="custom") - with reraise_exceptions(), \ - create_ui(foo, dict(view=view)) as ui: - editor, = ui.get_editors("name") - - set_text(editor, "NEW") - process_cascade_events() - + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + with self.assertTraitChanges(foo, "name", count=3): + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) self.assertEqual(foo.name, "NEW") + def test_custom_auto_set_false_update_text(self): # the auto_set flag is disregard for custom editor. foo = Foo() view = get_view(auto_set=False, style="custom") - with reraise_exceptions(), \ - create_ui(foo, dict(view=view)) as ui: - editor, = ui.get_editors("name") - - set_text(editor, "NEW") - process_cascade_events() - - key_press_return(editor) - process_cascade_events() - + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + tester.find_by_name(ui, "name").perform(command.KeyClick("Enter")) self.assertEqual(foo.name, "NEW\n") + @unittest.skipUnless( Version(TRAITS_VERSION) >= Version("6.1.0"), "This test requires traits >= 6.1.0" @@ -264,9 +215,13 @@ def test_format_func_used(self): Item("name", format_func=lambda s: s.upper()), Item("nickname"), ) - with reraise_exceptions(), \ - create_ui(foo, dict(view=view)) as ui: - name_editor, = ui.get_editors("name") - nickname_editor, = ui.get_editors("nickname") - self.assertEqual(get_text(name_editor), "WILLIAM") - self.assertEqual(get_text(nickname_editor), "bill") + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + display_name = ( + tester.find_by_name(ui, "name").inspect(query.DisplayedText()) + ) + display_nickname = ( + tester.find_by_name(ui, "nickname").inspect(query.DisplayedText()) + ) + self.assertEqual(display_name, "WILLIAM") + self.assertEqual(display_nickname, "bill") From 8a59b57e7780c8bc384a57ed382ea349a73680b7 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 24 Aug 2020 16:09:46 -0500 Subject: [PATCH 28/60] missed in merge --- traitsui/testing/tester/wx/default_registry.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 076e931e7..b9feddf53 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -72,8 +72,6 @@ def get_wx_object_registry(): target_class=wx.Button, interaction_class=command.MouseClick, handler=helpers.mouse_click_button -<<<<<<< HEAD -======= ) registry.register_handler( @@ -92,7 +90,6 @@ def get_wx_object_registry(): target_class=wx.Window, interaction_class=query.DisplayedText, handler=lambda wrapper, _: wrapper.target.GetLabel() ->>>>>>> ui-tester-api-updates2 ) return registry From 77f59038d9e9845310a14fb27864bd1e79077f63 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 25 Aug 2020 13:33:43 -0500 Subject: [PATCH 29/60] pre-master merge --- .../testing/tester/wx/default_registry.py | 13 +++- traitsui/testing/tester/wx/helpers.py | 2 +- .../tester/wx/implementation/text_editor.py | 2 +- .../tester/wx/tests/test_default_registry.py | 78 +++++++++++++++++++ 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index b9feddf53..4059d5fe4 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -54,7 +54,7 @@ def get_wx_object_registry(): registry.register_handler( target_class=wx.TextCtrl, interaction_class=command.KeyClick, - handler=helpers.key_press_text_ctrl + handler=helpers.key_click_text_ctrl ) registry.register_handler( target_class=wx.TextCtrl, @@ -65,9 +65,18 @@ def get_wx_object_registry(): target_class=wx.StaticText, interaction_class=query.DisplayedText, handler=lambda wrapper, action: ( - wrapper.target.GetLabel() + wrapper.target.label ), ) + registry.register_handler( + target_class=wx.TextCtrl, + interaction_class=query.DisplayedText, + handler=lambda wrapper, action: ( + wrapper.target.GetValue() + ), + ) + + registry.register_handler( target_class=wx.Button, interaction_class=command.MouseClick, diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 3c2755e90..8170514e5 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -62,7 +62,7 @@ def mouse_click_ImageButton(wrapper, interaction): control.ProcessEvent(left_up_event) -def key_press_text_ctrl(wrapper, interaction): +def key_click_text_ctrl(wrapper, interaction): control = wrapper.target if interaction.key == "Enter": if not control.HasFocus(): diff --git a/traitsui/testing/tester/wx/implementation/text_editor.py b/traitsui/testing/tester/wx/implementation/text_editor.py index e17f3e1e1..5d4cafb1f 100644 --- a/traitsui/testing/tester/wx/implementation/text_editor.py +++ b/traitsui/testing/tester/wx/implementation/text_editor.py @@ -10,7 +10,7 @@ # from traitsui.wx.text_editor import CustomEditor, ReadonlyEditor, SimpleEditor -from traitsui.testing import locator +from traitsui.testing.tester import locator def register(registry): diff --git a/traitsui/testing/tester/wx/tests/test_default_registry.py b/traitsui/testing/tester/wx/tests/test_default_registry.py index c31725354..b4231c6e1 100644 --- a/traitsui/testing/tester/wx/tests/test_default_registry.py +++ b/traitsui/testing/tester/wx/tests/test_default_registry.py @@ -71,3 +71,81 @@ def test_mouse_click_disabled_button(self): # then self.assertEqual(handler.call_count, 0) + + def test_key_sequence(self): + textbox = wx.TextCtrl() + + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + + wrapper.perform(command.KeySequence("abc")) + + self.assertEqual(textbox.value, "abc") + + + +"""def test_key_sequence(self): + textbox = QtGui.QLineEdit() + change_slot = mock.Mock() + textbox.textEdited.connect(change_slot) + + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + + # when + wrapper.perform(command.KeySequence("abc")) + # then + self.assertEqual(textbox.text(), "abc") + # each keystroke fires a signal + self.assertEqual(change_slot.call_count, 3) + + + def test_key_sequence_disabled(self): + textbox = QtGui.QLineEdit() + textbox.setEnabled(False) + + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + + # then + # this will fail, because one should not be allowed to set + # cursor on the widget to type anything + with self.assertRaises(Disabled): + wrapper.perform(command.KeySequence("abc")) + + + def test_key_click(self): + textbox = QtGui.QLineEdit() + change_slot = mock.Mock() + textbox.editingFinished.connect(change_slot) + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + # sanity check + wrapper.perform(command.KeySequence("abc")) + self.assertEqual(change_slot.call_count, 0) + + wrapper.perform(command.KeyClick("Enter")) + self.assertEqual(change_slot.call_count, 1) + + + def test_key_click_disabled(self): + textbox = QtGui.QLineEdit() + textbox.setEnabled(False) + change_slot = mock.Mock() + textbox.editingFinished.connect(change_slot) + wrapper = UIWrapper( + target=textbox, + registries=[default_registry.get_default_registry()], + ) + + with self.assertRaises(Disabled): + wrapper.perform(command.KeyClick("Enter")) + self.assertEqual(change_slot.call_count, 0)""" From 8428778b6f0878b7959f99554031c81fe83ba7c6 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 25 Aug 2020 13:45:59 -0500 Subject: [PATCH 30/60] more changes to align with master (recreate updates from ui-tester-api-updates2 that was recently merged) --- traitsui/testing/tester/locator.py | 19 --- .../testing/tester/tests/test_ui_wrapper.py | 121 ------------------ traitsui/testing/tester/ui_wrapper.py | 51 -------- 3 files changed, 191 deletions(-) diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index ffdb61cf2..d4e0b36b1 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -35,22 +35,3 @@ class TargetByName: """ def __init__(self, name): self.name = name - - -class DefaultTarget: - """ A locator for locating a default target. In some cases, handlers may - not be registered for certain interactions on a given target. Instead - there may be a solver registered a locator_class of DefaultTarget and a - target_class of the original target. The registered solver will then - return some generic (toolkit specific) object, for which frequently used - handlers will be registered. If user tries to perform the intetractions - of these handlers on the original object, machinery is in place for the - UIWrapper to try and locate a DefaultTarget and then look for the needed - handler automatically, if it can not find a handler directly for the - original target and interaction. - - Note: This object will likely be used by developers implementing solvers/ - handlers. For those just using UITester, this object should work its magic - under-the-hood, and not be needed directly. - """ - pass diff --git a/traitsui/testing/tester/tests/test_ui_wrapper.py b/traitsui/testing/tester/tests/test_ui_wrapper.py index 43b0fbf3d..33d32b452 100644 --- a/traitsui/testing/tester/tests/test_ui_wrapper.py +++ b/traitsui/testing/tester/tests/test_ui_wrapper.py @@ -127,13 +127,6 @@ def get_handler(self, target_class, interaction_class): supported=[int], ) - def get_solver(self, target_class, interaction_class): - raise LocationNotSupported( - target_class=None, - locator_class=None, - supported=[], - ) - class EmptyRegistry2: def get_handler(self, target_class, interaction_class): @@ -143,13 +136,6 @@ def get_handler(self, target_class, interaction_class): supported=[float], ) - def get_solver(self, target_class, interaction_class): - raise LocationNotSupported( - target_class=None, - locator_class=None, - supported=[], - ) - wrapper = example_ui_wrapper( registries=[EmptyRegistry1(), EmptyRegistry2()], ) @@ -303,110 +289,3 @@ def handler(wrapper, action): with self.assertRaises(ZeroDivisionError): wrapper.perform(None) - - -class Dummy1: - pass - - -class Dummy2: - pass - - -class DummyInteraction: - pass - - -class DummyInteraction2: - pass - - -class DummyInteraction3: - pass - - -@requires_toolkit([ToolkitName.qt, ToolkitName.wx]) -class TestUIWrapperDefaultTarget(unittest.TestCase): - - def test_interaction_from_DefaultTarget(self): - registry = TargetRegistry() - registry.register_solver( - target_class=Dummy1, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: Dummy2(), - ) - - registry.register_handler( - target_class=Dummy2, - interaction_class=DummyInteraction, - handler=lambda wrapper, _: 1, - ) - - wrapper = UIWrapper( - target=Dummy1(), - registries=[registry], - ) - - self.assertEqual(wrapper.inspect(DummyInteraction()), 1) - - def test_supported_interactions_through_DefaultTarget(self): - registry = TargetRegistry() - registry.register_solver( - target_class=Dummy1, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: Dummy2(), - ) - registry.register_handler( - target_class=Dummy1, - interaction_class=DummyInteraction3, - handler=lambda wrapper, _: 1, - ) - registry.register_handler( - target_class=Dummy2, - interaction_class=DummyInteraction2, - handler=lambda wrapper, _: 1, - ) - - wrapper = UIWrapper( - target=Dummy1(), - registries=[registry], - ) - - with self.assertRaises(InteractionNotSupported) as exception_context: - wrapper.inspect(DummyInteraction()) - self.assertEqual(len(exception_context.exception.supported), 2) - - def test_location_not_supported_from_default_target_handler(self): - # Test we don't mask or misrepresent a LocationNotSupported error from - # a handler on the default target - registry = TargetRegistry() - registry.register_solver( - target_class=Dummy1, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: Dummy2(), - ) - - exception = LocationNotSupported( - target_class=None, - locator_class=None, - supported=[], - ) - - def bad_handler(wrapper, _): - # probably programming error - raise exception - - registry.register_handler( - target_class=Dummy2, - interaction_class=DummyInteraction, - handler=bad_handler, - ) - - wrapper = UIWrapper( - target=Dummy1(), - registries=[registry], - ) - with self.assertRaises(LocationNotSupported) as exception_context: - wrapper.inspect(DummyInteraction()) - - self.assertIs(exception_context.exception, exception) diff --git a/traitsui/testing/tester/ui_wrapper.py b/traitsui/testing/tester/ui_wrapper.py index 95996e40c..9821f7602 100644 --- a/traitsui/testing/tester/ui_wrapper.py +++ b/traitsui/testing/tester/ui_wrapper.py @@ -161,63 +161,12 @@ def _perform_or_inspect(self, interaction): with _event_processed(): return handler(self, interaction) - try_DefaultTarget = self._try_interaction_via_DefaultTarget(supported, interaction) - if try_DefaultTarget != -1: - return try_DefaultTarget - raise InteractionNotSupported( target_class=self.target.__class__, interaction_class=interaction.__class__, supported=supported, ) - def _try_interaction_via_DefaultTarget(self, supported, interaction): - """ There may not be a handler registered for an interaction on - the original target (which is represented by self). Instead, it - may be registered for DefaultTarget. To address this possibility, - this method attempts to resolve from the current target to a - DefaultTarget, and then sees if there is an appropriate handler - that can perform the given interaction on DefaultTarget instead. If - so, that handler will ultimately be called and this method returns - the result. If not, any interactions for which there are handlers - registered for DefaultTarget will be added to the list of supported - interactiontypes. - - Parameters - ---------- - supported : List - A list of the interactions currently known to be supported for - the wrapper object. If no handler is found for the given - interaction with DefaultTarget as the target_class, any handlers - supported for DefaultTarget are added to the list. - interaction : instance of interaction type - An object defining the interaction. - - Returns - ------- - Any - The output of the call to the appropriate handler (if found) - None - If no appropriate handler is found, the method returns None, and - simply adds to the supported list if necessary. - """ - try: - default_target = self.locate(locator.DefaultTarget()) - # If we can't solve from the current target to DefaultTarget this - # interaction isn't supported, and there is nothing to add to supported - except LocationNotSupported: - pass - else: - # if we can locate a DefaultTarget try to perform the - # interactation on it instead - try: - return default_target._perform_or_inspect(interaction) - # if we can't add to supported - except InteractionNotSupported as e: - supported.extend(e.supported) - return -1 - - def _get_next_target(self, location): """ Return the next UI target from the given location. From fb3a535826ae077ac971c5bfbf6b56d6e3a8047e Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 25 Aug 2020 15:29:15 -0500 Subject: [PATCH 31/60] make refactor changes (since removal of DefaultTarget) --- traitsui/testing/tester/qt4/helpers.py | 16 ++--- .../tester/qt4/implementation/text_editor.py | 64 ++++++++++++++++-- .../testing/tester/wx/default_registry.py | 67 ------------------- traitsui/testing/tester/wx/helpers.py | 10 ++- .../tester/wx/implementation/text_editor.py | 56 ++++++++++++++-- 5 files changed, 119 insertions(+), 94 deletions(-) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 140452ae0..808197d12 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -65,13 +65,13 @@ def mouse_click_qwidget(control, delay): delay=delay, ) -def key_sequence_qwidget(wrapper, interaction): - if not wrapper.target.isEnabled(): - raise Disabled("{!r} is disabled.".format(wrapper.target)) - QTest.keyClicks(wrapper.target, interaction.sequence, delay=wrapper.delay) +def key_sequence_qwidget(control, interaction, delay): + if not control.isEnabled(): + raise Disabled("{!r} is disabled.".format(control)) + QTest.keyClicks(control, interaction.sequence, delay=delay) -def key_click_qwidget(wrapper, interaction): - if not wrapper.target.isEnabled(): - raise Disabled("{!r} is disabled.".format(wrapper.target)) - key_click(wrapper.target, interaction.key, delay=wrapper.delay) +def key_click_qwidget(control, interaction, delay): + if not control.isEnabled(): + raise Disabled("{!r} is disabled.".format(control)) + key_click(control, interaction.key, delay=delay) diff --git a/traitsui/testing/tester/qt4/implementation/text_editor.py b/traitsui/testing/tester/qt4/implementation/text_editor.py index ceb43db4f..326098507 100644 --- a/traitsui/testing/tester/qt4/implementation/text_editor.py +++ b/traitsui/testing/tester/qt4/implementation/text_editor.py @@ -8,17 +8,69 @@ # # Thanks for using Enthought open source! # -from traitsui.testing.tester import locator +from pyface.qt import QtGui + +from traitsui.testing.tester import command, query +from traitsui.testing.tester.qt4 import helpers from traitsui.qt4.text_editor import CustomEditor, ReadonlyEditor, SimpleEditor + +def simple_DisplayedText_handler(wrapper, interaction): + ''' Handler for SimpleEditor to handle query.DisplayedText interactions. + + Parameters + ---------- + wrapper : UIWrapper + the UIWrapper object wrapping the SimpleEditor + interaction : Instance of query.DisplayedText + This arguiment is not used by this function. It is included so that + the function matches the standard format for a handler. The intended + interaction should always be query.DisplayedText + + Notes + ----- + Qt SimpleEditors occassionally use QtGui.QTextEdit as their control, and + other times use QtGui.QLineEdit + ''' + if isinstance(wrapper.target.control, QtGui.QLineEdit): + return wrapper.target.control.displayText() + elif isinstance(wrapper.target.control, QtGui.QTextEdit): + return wrapper.target.control.toPlainText() + def register(registry): """ Register actions for the given registry. If there are any conflicts, an error will occur. """ + + handlers = [ + (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( + wrapper.target.control, interaction, wrapper.delay))), + (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( + wrapper.target.control, interaction, wrapper.delay))), + (command.MouseClick,(lambda wrapper, _: helpers.mouse_click_qwidget( + wrapper.target.control, wrapper.delay))), + ] for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: - registry.register_solver( - target_class=target_class, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: wrapper.target.control, - ) + for interaction_class, handler in handlers: + registry.register_handler( + target_class=target_class, + interaction_class=interaction_class, + handler=handler, + ) + + registry.register_handler( + target_class=CustomEditor, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.control.toPlainText(), + ) + registry.register_handler( + target_class=SimpleEditor, + interaction_class=query.DisplayedText, + handler=simple_DisplayedText_handler, + ) + registry.register_handler( + target_class=ReadonlyEditor, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.control.text(), + ) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index bf694cf9e..8ce4465b7 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -9,11 +9,7 @@ # Thanks for using Enthought open source! # -import wx - -from traitsui.testing.tester import command, query from traitsui.testing.tester.registry import TargetRegistry -from traitsui.testing.tester.wx import helpers from traitsui.testing.tester.wx.implementation import ( button_editor, text_editor, @@ -38,66 +34,3 @@ def get_default_registry(): text_editor.register(registry) return registry - - -def get_wx_object_registry(): - """ Creates a generic registry for handling/solving wx objects. (i.e. - this registry is independent of TraitsUI) - - Returns - ------- - registry : TargetRegistry - Registry containing wx specific generic handlers and solvers. - """ - registry = TargetRegistry() - - registry.register_handler( - target_class=wx.TextCtrl, - interaction_class=command.KeyClick, - handler=helpers.key_click_text_ctrl - ) - registry.register_handler( - target_class=wx.TextCtrl, - interaction_class=command.KeySequence, - handler=helpers.key_sequence_text_ctrl - ) - registry.register_handler( - target_class=wx.StaticText, - interaction_class=query.DisplayedText, - handler=lambda wrapper, action: ( - wrapper.target.label - ), - ) - registry.register_handler( - target_class=wx.TextCtrl, - interaction_class=query.DisplayedText, - handler=lambda wrapper, action: ( - wrapper.target.GetValue() - ), - ) - - - registry.register_handler( - target_class=wx.Button, - interaction_class=command.MouseClick, - handler=helpers.mouse_click_button - ) - - registry.register_handler( - target_class=wx.Button, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.GetLabel() - ) - - registry.register_handler( - target_class=wx.Window, - interaction_class=command.MouseClick, - handler=helpers.mouse_click_ImageButton - ) - - registry.register_handler( - target_class=wx.Window, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.GetLabel() - ) - diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index ffd350224..551955ff3 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -31,24 +31,22 @@ def mouse_click_button(control, delay): control.ProcessEvent(click_event) -def key_click_text_ctrl(wrapper, interaction): - control = wrapper.target +def key_click_text_ctrl(control, interaction, delay): if interaction.key == "Enter": if not control.HasFocus(): control.SetFocus() - wx.MilliSleep(wrapper.delay) + wx.MilliSleep(delay) event = wx.CommandEvent(wx.EVT_TEXT_ENTER.typeId, control.GetId()) control.ProcessEvent(event) else: raise ValueError("Only supported Enter key.") -def key_sequence_text_ctrl(wrapper, interaction): - control = wrapper.target +def key_sequence_text_ctrl(control, interaction, delay): if not control.HasFocus(): control.SetFocus() for char in interaction.sequence: - wx.MilliSleep(wrapper.delay) + wx.MilliSleep(delay) if char == "\b": pos = control.GetInsertionPoint() control.Remove(max(0, pos - 1), pos) diff --git a/traitsui/testing/tester/wx/implementation/text_editor.py b/traitsui/testing/tester/wx/implementation/text_editor.py index 5d4cafb1f..71f053e92 100644 --- a/traitsui/testing/tester/wx/implementation/text_editor.py +++ b/traitsui/testing/tester/wx/implementation/text_editor.py @@ -9,14 +9,56 @@ # Thanks for using Enthought open source! # +import wx + from traitsui.wx.text_editor import CustomEditor, ReadonlyEditor, SimpleEditor -from traitsui.testing.tester import locator +from traitsui.testing.tester import command, query +from traitsui.testing.tester.wx import helpers + + +def readonly_DisplayedText_handler(wrapper, interaction): + ''' Handler for ReadonlyEditor to handle query.DisplayedText interactions. + + Parameters + ---------- + wrapper : UIWrapper + the UIWrapper object wrapping the ReadonlyEditor + interaction : Instance of query.DisplayedText + This arguiment is not used by this function. It is included so that + the function matches the standard format for a handler. The intended + interaction should always be query.DisplayedText + + Notes + ----- + wx Readonly Editors occassionally use wx.TextCtrl as their control, and + other times use wx.StaticText. + ''' + if isinstance(wrapper.target.control, wx.TextCtrl): + return wrapper.target.control.GetValue() + elif isinstance(wrapper.target.control, wx.StaticText): + return wrapper.target.control.label def register(registry): - for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: - registry.register_solver( - target_class=target_class, - locator_class=locator.DefaultTarget, - solver=lambda wrapper, _: wrapper.target.control, - ) + + handlers = [ + (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_text_ctrl( + wrapper.target.control, interaction, wrapper.delay))), + (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( + wrapper.target.control, interaction, wrapper.delay))), + (query.DisplayedText, lambda wrapper, _: wrapper.target.control.GetValue()) + ] + + for target_class in [CustomEditor, SimpleEditor]: + for interaction_class, handler in handlers: + registry.register_handler( + target_class=target_class, + interaction_class=interaction_class, + solver=handler, + ) + + registry.register_handler( + target_class=ReadonlyEditor, + interaction_class=query.DisplayedText, + solver=readonly_DisplayedText_handler, + ) From 8b0f2eb425a52be90284d0f8bbe98a2696bd9cd8 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 25 Aug 2020 16:50:08 -0500 Subject: [PATCH 32/60] updating test_helpers --- .../tester/qt4/tests/test_default_registry.py | 133 --------------- .../testing/tester/qt4/tests/test_helpers.py | 52 ++++++ traitsui/testing/tester/wx/helpers.py | 6 + .../tester/wx/tests/test_default_registry.py | 151 ------------------ .../testing/tester/wx/tests/test_helpers.py | 36 +++++ 5 files changed, 94 insertions(+), 284 deletions(-) delete mode 100644 traitsui/testing/tester/qt4/tests/test_default_registry.py delete mode 100644 traitsui/testing/tester/wx/tests/test_default_registry.py diff --git a/traitsui/testing/tester/qt4/tests/test_default_registry.py b/traitsui/testing/tester/qt4/tests/test_default_registry.py deleted file mode 100644 index d5f8d816d..000000000 --- a/traitsui/testing/tester/qt4/tests/test_default_registry.py +++ /dev/null @@ -1,133 +0,0 @@ -# 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! -# - -import unittest -from unittest import mock - -from traits.testing.api import UnittestTools -from traitsui.tests._tools import ( - is_qt, - requires_toolkit, - ToolkitName, -) -from traitsui.testing.tester import command, query -from traitsui.testing.tester.exceptions import Disabled -from traitsui.testing.tester.ui_wrapper import UIWrapper - -try: - from pyface.qt import QtGui - from traitsui.testing.tester.qt4 import default_registry -except ImportError: - if is_qt(): - raise - - -@requires_toolkit([ToolkitName.qt]) -class TestInteractions(unittest.TestCase): - - def test_mouse_click(self): - button = QtGui.QPushButton() - click_slot = mock.Mock() - button.clicked.connect(click_slot) - - wrapper = UIWrapper( - target=button, - registries=[default_registry.get_default_registry()], - ) - - wrapper.perform(command.MouseClick()) - - self.assertEqual(click_slot.call_count, 1) - - def test_mouse_click_disabled(self): - button = QtGui.QPushButton() - button.setEnabled(False) - - click_slot = mock.Mock() - button.clicked.connect(click_slot) - - wrapper = UIWrapper( - target=button, - registries=[default_registry.get_default_registry()], - ) - - # when - # clicking won't fail, it just does not do anything. - # This is consistent with the actual UI. - wrapper.perform(command.MouseClick()) - - # then - self.assertEqual(click_slot.call_count, 0) - - - def test_key_sequence(self): - textbox = QtGui.QLineEdit() - change_slot = mock.Mock() - textbox.textEdited.connect(change_slot) - - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - - # when - wrapper.perform(command.KeySequence("abc")) - # then - self.assertEqual(textbox.text(), "abc") - # each keystroke fires a signal - self.assertEqual(change_slot.call_count, 3) - - - def test_key_sequence_disabled(self): - textbox = QtGui.QLineEdit() - textbox.setEnabled(False) - - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - - # then - # this will fail, because one should not be allowed to set - # cursor on the widget to type anything - with self.assertRaises(Disabled): - wrapper.perform(command.KeySequence("abc")) - - - def test_key_click(self): - textbox = QtGui.QLineEdit() - change_slot = mock.Mock() - textbox.editingFinished.connect(change_slot) - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - # sanity check - wrapper.perform(command.KeySequence("abc")) - self.assertEqual(change_slot.call_count, 0) - - wrapper.perform(command.KeyClick("Enter")) - self.assertEqual(change_slot.call_count, 1) - - - def test_key_click_disabled(self): - textbox = QtGui.QLineEdit() - textbox.setEnabled(False) - change_slot = mock.Mock() - textbox.editingFinished.connect(change_slot) - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - - with self.assertRaises(Disabled): - wrapper.perform(command.KeyClick("Enter")) - self.assertEqual(change_slot.call_count, 0) diff --git a/traitsui/testing/tester/qt4/tests/test_helpers.py b/traitsui/testing/tester/qt4/tests/test_helpers.py index 0a78c2120..3af3c0f53 100644 --- a/traitsui/testing/tester/qt4/tests/test_helpers.py +++ b/traitsui/testing/tester/qt4/tests/test_helpers.py @@ -17,8 +17,11 @@ requires_toolkit, ToolkitName, ) +from traitsui.testing.tester import command +from traitsui.testing.tester.exceptions import Disabled from traitsui.testing.tester.qt4 import helpers + try: from pyface.qt import QtGui except ImportError: @@ -52,3 +55,52 @@ def test_mouse_click_disabled(self): # then self.assertEqual(click_slot.call_count, 0) + + def test_key_sequence(self): + textbox = QtGui.QLineEdit() + change_slot = mock.Mock() + textbox.textEdited.connect(change_slot) + + # when + helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) + + # then + self.assertEqual(textbox.text(), "abc") + # each keystroke fires a signal + self.assertEqual(change_slot.call_count, 3) + + + def test_key_sequence_disabled(self): + textbox = QtGui.QLineEdit() + textbox.setEnabled(False) + + # this will fail, because one should not be allowed to set + # cursor on the widget to type anything + with self.assertRaises(Disabled): + helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) + + + def test_key_click(self): + textbox = QtGui.QLineEdit() + change_slot = mock.Mock() + textbox.editingFinished.connect(change_slot) + + # sanity check + helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) + self.assertEqual(change_slot.call_count, 0) + + helpers.key_click_qwidget(textbox, command.KeyClick("Enter"), 0) + self.assertEqual(change_slot.call_count, 1) + + + def test_key_click_disabled(self): + textbox = QtGui.QLineEdit() + textbox.setEnabled(False) + change_slot = mock.Mock() + textbox.editingFinished.connect(change_slot) + + + with self.assertRaises(Disabled): + helpers.key_click_qwidget(textbox, command.KeyClick("Enter"), 0) + self.assertEqual(change_slot.call_count, 0) + diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 551955ff3..7bf2682be 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -11,6 +11,8 @@ import wx +from traitsui.testing.tester.exceptions import Disabled + def mouse_click_button(control, delay): """ Performs a mouce click on a wx button. @@ -32,6 +34,8 @@ def mouse_click_button(control, delay): def key_click_text_ctrl(control, interaction, delay): + if not control.IsEditable(): + raise Disabled("{!r} is disabled.".format(control)) if interaction.key == "Enter": if not control.HasFocus(): control.SetFocus() @@ -43,6 +47,8 @@ def key_click_text_ctrl(control, interaction, delay): def key_sequence_text_ctrl(control, interaction, delay): + if not control.IsEditable(): + raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): control.SetFocus() for char in interaction.sequence: diff --git a/traitsui/testing/tester/wx/tests/test_default_registry.py b/traitsui/testing/tester/wx/tests/test_default_registry.py deleted file mode 100644 index b4231c6e1..000000000 --- a/traitsui/testing/tester/wx/tests/test_default_registry.py +++ /dev/null @@ -1,151 +0,0 @@ -# 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! -# - -import unittest -from unittest import mock - -from traitsui.testing.tester import command -from traitsui.testing.tester.ui_wrapper import UIWrapper - -from traitsui.tests._tools import ( - is_wx, - requires_toolkit, - ToolkitName, -) - -try: - import wx - from traitsui.testing.tester.wx import default_registry -except ImportError: - if is_wx(): - raise - - -@requires_toolkit([ToolkitName.wx]) -class TestInteractions(unittest.TestCase): - - def setUp(self): - self.app = wx.App() - self.frame = wx.Frame(None) - self.frame.Show() - - def tearDown(self): - wx.CallAfter(self.app.ExitMainLoop) - self.app.MainLoop() - - def test_mouse_click(self): - handler = mock.Mock() - button = wx.Button(self.frame) - button.Bind(wx.EVT_BUTTON, handler) - wrapper = UIWrapper( - target=button, - registries=[default_registry.get_default_registry()], - ) - - # when - wrapper.perform(command.MouseClick()) - - # then - self.assertEqual(handler.call_count, 1) - - def test_mouse_click_disabled_button(self): - handler = mock.Mock() - button = wx.Button(self.frame) - button.Bind(wx.EVT_BUTTON, handler) - button.Enable(False) - wrapper = UIWrapper( - target=button, - registries=[default_registry.get_default_registry()], - ) - - # when - wrapper.perform(command.MouseClick()) - - # then - self.assertEqual(handler.call_count, 0) - - def test_key_sequence(self): - textbox = wx.TextCtrl() - - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - - wrapper.perform(command.KeySequence("abc")) - - self.assertEqual(textbox.value, "abc") - - - -"""def test_key_sequence(self): - textbox = QtGui.QLineEdit() - change_slot = mock.Mock() - textbox.textEdited.connect(change_slot) - - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - - # when - wrapper.perform(command.KeySequence("abc")) - # then - self.assertEqual(textbox.text(), "abc") - # each keystroke fires a signal - self.assertEqual(change_slot.call_count, 3) - - - def test_key_sequence_disabled(self): - textbox = QtGui.QLineEdit() - textbox.setEnabled(False) - - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - - # then - # this will fail, because one should not be allowed to set - # cursor on the widget to type anything - with self.assertRaises(Disabled): - wrapper.perform(command.KeySequence("abc")) - - - def test_key_click(self): - textbox = QtGui.QLineEdit() - change_slot = mock.Mock() - textbox.editingFinished.connect(change_slot) - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - # sanity check - wrapper.perform(command.KeySequence("abc")) - self.assertEqual(change_slot.call_count, 0) - - wrapper.perform(command.KeyClick("Enter")) - self.assertEqual(change_slot.call_count, 1) - - - def test_key_click_disabled(self): - textbox = QtGui.QLineEdit() - textbox.setEnabled(False) - change_slot = mock.Mock() - textbox.editingFinished.connect(change_slot) - wrapper = UIWrapper( - target=textbox, - registries=[default_registry.get_default_registry()], - ) - - with self.assertRaises(Disabled): - wrapper.perform(command.KeyClick("Enter")) - self.assertEqual(change_slot.call_count, 0)""" diff --git a/traitsui/testing/tester/wx/tests/test_helpers.py b/traitsui/testing/tester/wx/tests/test_helpers.py index da8fc0d13..b4aafeb90 100644 --- a/traitsui/testing/tester/wx/tests/test_helpers.py +++ b/traitsui/testing/tester/wx/tests/test_helpers.py @@ -12,6 +12,8 @@ import unittest from unittest import mock +from traitsui.testing.tester import command, query +from traitsui.testing.tester.exceptions import Disabled from traitsui.testing.tester.wx import helpers from traitsui.tests._tools import ( @@ -61,3 +63,37 @@ def test_mouse_click_disabled_button(self): # then self.assertEqual(handler.call_count, 0) + + + def test_key_sequence(self): + textbox = wx.TextCtrl(self.frame) + + helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) + + self.assertEqual(textbox.value, "abc") + + def test_key_sequence_disabled(self): + textbox = wx.TextCtrl(self.frame) + textbox.SetEditable(False) + + with self.assertRaises(Disabled): + helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) + + + def test_key_click(self): + textbox = wx.TextCtrl(self.frame) + + # sanity check + helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) + self.assertEqual(textbox.GetLastPosition(), 0) + + helpers.key_click_text_ctrl(textbox, command.KeyClick("Enter"), 0) + self.assertEqual(textbox.GetLastPosition(), 2) + + + def test_key_click_disabled(self): + textbox = wx.TextCtrl(self.frame) + textbox.SetEditable(False) + + with self.assertRaises(Disabled): + helpers.key_click_text_ctrl(textbox, command.KeyClick("Enter"), 0) From 55e0bfa92a11b7d0d858a842b639ed91b9d34398 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 25 Aug 2020 17:13:38 -0500 Subject: [PATCH 33/60] fix some test and comment one out (unsure how to test key_click on wx) --- traitsui/testing/tester/wx/helpers.py | 18 +++++++++++++++++- .../testing/tester/wx/tests/test_helpers.py | 6 +++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 7bf2682be..71f871e57 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -46,6 +46,19 @@ def key_click_text_ctrl(control, interaction, delay): raise ValueError("Only supported Enter key.") +"""def key_sequence_text_ctrl(control, interaction, delay): + if not control.IsEditable(): + raise Disabled("{!r} is disabled.".format(control)) + if not control.HasFocus(): + control.SetFocus() + for char in interaction.sequence: + wx.MilliSleep(delay) + if char == "\b": + pos = control.GetInsertionPoint() + control.Remove(max(0, pos - 1), pos) + else: + control.AppendText(char)""" + def key_sequence_text_ctrl(control, interaction, delay): if not control.IsEditable(): raise Disabled("{!r} is disabled.".format(control)) @@ -57,4 +70,7 @@ def key_sequence_text_ctrl(control, interaction, delay): pos = control.GetInsertionPoint() control.Remove(max(0, pos - 1), pos) else: - control.AppendText(char) + char_key_event = wx.KeyEvent(wx.wxEVT_CHAR) + char_key_event.SetUnicodeKey(ord(char)) + control.EmulateKeyPress(char_key_event) + diff --git a/traitsui/testing/tester/wx/tests/test_helpers.py b/traitsui/testing/tester/wx/tests/test_helpers.py index b4aafeb90..11076c06f 100644 --- a/traitsui/testing/tester/wx/tests/test_helpers.py +++ b/traitsui/testing/tester/wx/tests/test_helpers.py @@ -70,7 +70,7 @@ def test_key_sequence(self): helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) - self.assertEqual(textbox.value, "abc") + self.assertEqual(textbox.Value, "abc") def test_key_sequence_disabled(self): textbox = wx.TextCtrl(self.frame) @@ -80,7 +80,7 @@ def test_key_sequence_disabled(self): helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) - def test_key_click(self): + """def test_key_click(self): textbox = wx.TextCtrl(self.frame) # sanity check @@ -88,7 +88,7 @@ def test_key_click(self): self.assertEqual(textbox.GetLastPosition(), 0) helpers.key_click_text_ctrl(textbox, command.KeyClick("Enter"), 0) - self.assertEqual(textbox.GetLastPosition(), 2) + self.assertEqual(textbox.GetLastPosition(), 2)""" def test_key_click_disabled(self): From 69b5618b52e88aa0ca766d0fb102ee5638f7cab7 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 08:00:39 -0500 Subject: [PATCH 34/60] reremoivng get_qobject_registry --- .../testing/tester/qt4/default_registry.py | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 50f1b87f9..865dfa483 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -38,53 +38,3 @@ def get_default_registry(): text_editor.register(registry) return registry - - -def get_qobject_registry(): - """ Creates a generic registry for handling/solving qt objects. (i.e. - this registry is independent of TraitsUI) - - Returns - ------- - registry : TargetRegistry - Registry containing qt specific generic handlers and solvers. - """ - registry = TargetRegistry() - - widget_classes = [ - QtGui.QLineEdit, - QtGui.QTextEdit, - QtGui.QPushButton, - ] - handlers = [ - (command.KeySequence, helpers.key_sequence_qwidget), - (command.KeyClick, helpers.key_click_qwidget), - (command.MouseClick, helpers.mouse_click_qwidget), - ] - for widget_class in widget_classes: - for interaction_class, handler in handlers: - registry.register_handler( - target_class=widget_class, - interaction_class=interaction_class, - handler=handler, - ) - - registry.register_handler( - target_class=QtGui.QLineEdit, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.displayText(), - ) - - registry.register_handler( - target_class=QtGui.QTextEdit, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.toPlainText(), - ) - - registry.register_handler( - target_class=QtGui.QPushButton, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.text(), - ) - - return registry From 5aeef9468355eacb6145a64e72ce3e95b19e25af Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 08:56:00 -0500 Subject: [PATCH 35/60] adding docstrings to qt helpers --- traitsui/testing/tester/qt4/helpers.py | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 808197d12..98d6fad97 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -18,6 +18,19 @@ from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP def key_click(widget, key, delay=0): + """ Performs a key click of the given key on the given widget after + a delay. + + Parameters + ---------- + widget : Qwidget + The Qt widget to be key clicked. + key : str + Standardized (pyface) name for a keyboard event. + e.g. "Enter", "Tab", "Space", "0", "1", "A", ... + delay : int + Time delay (in ms) in which the key click will be performed. + """ if "-" in key: *modifiers, key = key.split("-") else: @@ -66,12 +79,39 @@ def mouse_click_qwidget(control, delay): ) def key_sequence_qwidget(control, interaction, delay): + """ Performs simulated typing of a sequence of keys on the given widget + after a delay. + + Parameters + ---------- + control : Qwidget + The Qt widget to be acted on. + interaction : instance of command.KeySequence + The interaction (command) object holding the sequence of key inputs + to be simulated being typed + delay : int + Time delay (in ms) in which each key click in the sequence will be + performed. + """ if not control.isEnabled(): raise Disabled("{!r} is disabled.".format(control)) QTest.keyClicks(control, interaction.sequence, delay=delay) def key_click_qwidget(control, interaction, delay): + """ Performs simulated typing of a sequence of keys on the given widget + after a delay. + + Parameters + ---------- + control : Qwidget + The Qt widget to be acted on. + interaction : instance of command.KeyClick + The interaction (command) object holding the key input + to be simulated being typed + delay : int + Time delay (in ms) in which the key click will be performed. + """ if not control.isEnabled(): raise Disabled("{!r} is disabled.".format(control)) key_click(control, interaction.key, delay=delay) From d8baa03a7bd8a1fadb5fab6cc341665c8683a9dd Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 11:05:58 -0500 Subject: [PATCH 36/60] updating wx helpers --- .../testing/tester/qt4/default_registry.py | 4 - traitsui/testing/tester/qt4/helpers.py | 7 +- traitsui/testing/tester/wx/helpers.py | 112 +++++++++++++----- .../testing/tester/wx/tests/test_helpers.py | 10 +- 4 files changed, 90 insertions(+), 43 deletions(-) diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 865dfa483..9084a3bcd 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -9,11 +9,7 @@ # Thanks for using Enthought open source! # -from pyface.qt import QtGui - -from traitsui.testing.tester import command, query from traitsui.testing.tester.registry import TargetRegistry -from traitsui.testing.tester.qt4 import helpers from traitsui.testing.tester.qt4.implementation import ( button_editor, text_editor, diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 98d6fad97..7014c0d68 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -87,7 +87,7 @@ def key_sequence_qwidget(control, interaction, delay): control : Qwidget The Qt widget to be acted on. interaction : instance of command.KeySequence - The interaction (command) object holding the sequence of key inputs + The interaction object holding the sequence of key inputs to be simulated being typed delay : int Time delay (in ms) in which each key click in the sequence will be @@ -99,15 +99,14 @@ def key_sequence_qwidget(control, interaction, delay): def key_click_qwidget(control, interaction, delay): - """ Performs simulated typing of a sequence of keys on the given widget - after a delay. + """ Performs simulated typing of a key on the given widget after a delay. Parameters ---------- control : Qwidget The Qt widget to be acted on. interaction : instance of command.KeyClick - The interaction (command) object holding the key input + The interaction object holding the key input to be simulated being typed delay : int Time delay (in ms) in which the key click will be performed. diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 71f871e57..3d0ea558d 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -12,6 +12,61 @@ import wx from traitsui.testing.tester.exceptions import Disabled +from traitsui.wx.key_event_to_name import key_map as _KEY_MAP + + +def apply_modifiers(key_event, modifiers): + possible_modifiers = ["Alt", "Ctrl", "Meta", "Shift"] + modifier_map = {mod: (mod in modifiers) for mod in possible_modifiers} + key_event.SetAltDown(modifier_map["Alt"]) + key_event.SetControlDown(modifier_map["Ctrl"]) + key_event.SetMetaDown(modifier_map["Meta"]) + key_event.SetShiftDown(modifier_map["Shift"]) + + +def key_click(widget, key, delay=0): + """ Performs a key click of the given key on the given widget after + a delay. + + Parameters + ---------- + widget : wxObject + The wx Object to be clicked. + key : str + Standardized (pyface) name for a keyboard event. + e.g. "Enter", "Tab", "Space", "0", "1", "A", ... + delay : int + Time delay (in ms) in which the key click will be performed. + """ + if "-" in key: + *modifiers, key = key.split("-") + else: + modifiers = [] + + mapping = {name: event for event, name in _KEY_MAP.items()} + if key not in mapping: + if len(key) == 1: + try: + KEY = ord(key) + except: + raise ValueError( + "Unknown key {!r}. Expected one of these: {!r}, or a unicode character".format( + key, sorted(mapping) + )) + else: + wx.MilliSleep(delay) + key_event = wx.KeyEvent(wx.wxEVT_CHAR) + apply_modifiers(key_event, modifiers) + key_event.SetUnicodeKey(KEY) + widget.EmulateKeyPress(key_event) + + else: + wx.MilliSleep(delay) + KEY = mapping[key] + key_event = wx.KeyEvent(wx.wxEVT_CHAR) + apply_modifiers(key_event, modifiers) + key_event.SetKeyCode(mapping[key]) + widget.EmulateKeyPress(key_event) def mouse_click_button(control, delay): @@ -34,43 +89,44 @@ def mouse_click_button(control, delay): def key_click_text_ctrl(control, interaction, delay): - if not control.IsEditable(): - raise Disabled("{!r} is disabled.".format(control)) - if interaction.key == "Enter": - if not control.HasFocus(): - control.SetFocus() - wx.MilliSleep(delay) - event = wx.CommandEvent(wx.EVT_TEXT_ENTER.typeId, control.GetId()) - control.ProcessEvent(event) - else: - raise ValueError("Only supported Enter key.") - - -"""def key_sequence_text_ctrl(control, interaction, delay): + """ Performs simulated typing of a key on the given wxObject + after a delay. + + Parameters + ---------- + control : wxObject + The wx Object to be acted on. + interaction : instance of command.KeyClick + The interaction object holding the key input + to be simulated being typed + delay : int + Time delay (in ms) in which the key click will be performed. + """ if not control.IsEditable(): raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): control.SetFocus() - for char in interaction.sequence: - wx.MilliSleep(delay) - if char == "\b": - pos = control.GetInsertionPoint() - control.Remove(max(0, pos - 1), pos) - else: - control.AppendText(char)""" + key_click(control, interaction.key, delay) def key_sequence_text_ctrl(control, interaction, delay): + """ Performs simulated typing of a sequence of keys on the given wxObject + after a delay. + + Parameters + ---------- + control : wxObject + The wx Object to be acted on. + interaction : instance of command.KeySequence + The interaction object holding the sequence of key inputs + to be simulated being typed + delay : int + Time delay (in ms) in which each key click in the sequence will be + performed. + """ if not control.IsEditable(): raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): control.SetFocus() for char in interaction.sequence: - wx.MilliSleep(delay) - if char == "\b": - pos = control.GetInsertionPoint() - control.Remove(max(0, pos - 1), pos) - else: - char_key_event = wx.KeyEvent(wx.wxEVT_CHAR) - char_key_event.SetUnicodeKey(ord(char)) - control.EmulateKeyPress(char_key_event) + key_click(control, char, delay) diff --git a/traitsui/testing/tester/wx/tests/test_helpers.py b/traitsui/testing/tester/wx/tests/test_helpers.py index 11076c06f..86828ef2f 100644 --- a/traitsui/testing/tester/wx/tests/test_helpers.py +++ b/traitsui/testing/tester/wx/tests/test_helpers.py @@ -80,15 +80,11 @@ def test_key_sequence_disabled(self): helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) - """def test_key_click(self): + def test_key_click(self): textbox = wx.TextCtrl(self.frame) - - # sanity check - helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) - self.assertEqual(textbox.GetLastPosition(), 0) - helpers.key_click_text_ctrl(textbox, command.KeyClick("Enter"), 0) - self.assertEqual(textbox.GetLastPosition(), 2)""" + helpers.key_click_text_ctrl(textbox, command.KeyClick("A"), 0) + self.assertEqual(textbox.Value, "A") def test_key_click_disabled(self): From 02842809ee14e4577f9fd7db657a601466f1830b Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 11:44:58 -0500 Subject: [PATCH 37/60] simple helper test improvements --- .../testing/tester/qt4/tests/test_helpers.py | 35 +++++++++++++------ traitsui/testing/tester/wx/helpers.py | 8 ++--- .../testing/tester/wx/tests/test_helpers.py | 2 ++ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/traitsui/testing/tester/qt4/tests/test_helpers.py b/traitsui/testing/tester/qt4/tests/test_helpers.py index 3af3c0f53..6ae71c2c9 100644 --- a/traitsui/testing/tester/qt4/tests/test_helpers.py +++ b/traitsui/testing/tester/qt4/tests/test_helpers.py @@ -56,18 +56,24 @@ def test_mouse_click_disabled(self): # then self.assertEqual(click_slot.call_count, 0) + def test_key_sequence(self): - textbox = QtGui.QLineEdit() - change_slot = mock.Mock() - textbox.textEdited.connect(change_slot) + # test on different Qwidget objects + textboxes = [QtGui.QLineEdit(), QtGui.QTextEdit()] + for i, textbox in enumerate(textboxes): + change_slot = mock.Mock() + textbox.textChanged.connect(change_slot) - # when - helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) + # when + helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) - # then - self.assertEqual(textbox.text(), "abc") - # each keystroke fires a signal - self.assertEqual(change_slot.call_count, 3) + # then + if i == 0: + self.assertEqual(textbox.text(), "abc") + else: + self.assertEqual(textbox.toPlainText(), "abc") + # each keystroke fires a signal + self.assertEqual(change_slot.call_count, 3) def test_key_sequence_disabled(self): @@ -85,13 +91,22 @@ def test_key_click(self): change_slot = mock.Mock() textbox.editingFinished.connect(change_slot) - # sanity check + # sanity check on editingFinished signal helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) self.assertEqual(change_slot.call_count, 0) helpers.key_click_qwidget(textbox, command.KeyClick("Enter"), 0) self.assertEqual(change_slot.call_count, 1) + # test on a different Qwidget object - QtGui.QTextEdit() + textbox = QtGui.QTextEdit() + change_slot = mock.Mock() + # Now "Enter" should not finish editing, but instead go to next line + textbox.textChanged.connect(change_slot) + helpers.key_click_qwidget(textbox, command.KeyClick("Enter"), 0) + self.assertEqual(change_slot.call_count, 1) + self.assertEqual(textbox.toPlainText(), "\n") + def test_key_click_disabled(self): textbox = QtGui.QLineEdit() diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 3d0ea558d..bf672a31c 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -31,7 +31,7 @@ def key_click(widget, key, delay=0): Parameters ---------- widget : wxObject - The wx Object to be clicked. + The wx Object to be clicked. Should be wx.TextCtrl instance key : str Standardized (pyface) name for a keyboard event. e.g. "Enter", "Tab", "Space", "0", "1", "A", ... @@ -95,7 +95,7 @@ def key_click_text_ctrl(control, interaction, delay): Parameters ---------- control : wxObject - The wx Object to be acted on. + The wx Object to be acted on. Should be wx.TextCtrl instance interaction : instance of command.KeyClick The interaction object holding the key input to be simulated being typed @@ -108,6 +108,7 @@ def key_click_text_ctrl(control, interaction, delay): control.SetFocus() key_click(control, interaction.key, delay) + def key_sequence_text_ctrl(control, interaction, delay): """ Performs simulated typing of a sequence of keys on the given wxObject after a delay. @@ -115,7 +116,7 @@ def key_sequence_text_ctrl(control, interaction, delay): Parameters ---------- control : wxObject - The wx Object to be acted on. + The wx Object to be acted on. Should be wx.TextCtrl instance interaction : instance of command.KeySequence The interaction object holding the sequence of key inputs to be simulated being typed @@ -129,4 +130,3 @@ def key_sequence_text_ctrl(control, interaction, delay): control.SetFocus() for char in interaction.sequence: key_click(control, char, delay) - diff --git a/traitsui/testing/tester/wx/tests/test_helpers.py b/traitsui/testing/tester/wx/tests/test_helpers.py index 86828ef2f..5709598c8 100644 --- a/traitsui/testing/tester/wx/tests/test_helpers.py +++ b/traitsui/testing/tester/wx/tests/test_helpers.py @@ -85,6 +85,8 @@ def test_key_click(self): helpers.key_click_text_ctrl(textbox, command.KeyClick("A"), 0) self.assertEqual(textbox.Value, "A") + helpers.key_click_text_ctrl(textbox, command.KeyClick("Backspace"), 0) + self.assertEqual(textbox.Value, "") def test_key_click_disabled(self): From e1fdef629af8e458c82d78cc4dc9cdb0e8c67073 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 12:37:15 -0500 Subject: [PATCH 38/60] update test_text_editor --- traitsui/tests/editors/test_text_editor.py | 65 +++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index b49fe23c9..64c4194b2 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -150,6 +150,10 @@ def test_custom_editor_init_and_dispose(self): # Smoke test to test setup and tear down of an editor. self.check_editor_init_and_dispose(style="custom", auto_set=True) + def test_readonly_editor_init_and_dispose(self): + # Smoke test to test setup and tear down of an editor. + self.check_editor_init_and_dispose(style="readonly", auto_set=True) + def test_simple_editor_init_and_dispose_no_auto_set(self): # Smoke test to test setup and tear down of an editor. self.check_editor_init_and_dispose(style="simple", auto_set=False) @@ -165,7 +169,10 @@ def test_simple_auto_set_update_text(self): with tester.create_ui(foo, dict(view=view)) as ui: with self.assertTraitChanges(foo, "name", count=3): tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + # with auto-set the displayed name should match the name trait + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW") + self.assertEqual(display_name, foo.name) def test_simple_auto_set_false_do_not_update(self): @@ -174,9 +181,16 @@ def test_simple_auto_set_false_do_not_update(self): tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + # with auto-set as False the displayed name should match what has + # been typed not the trait itself, After "Enter" is pressed it + # should match the name trait + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa self.assertEqual(foo.name, "") + self.assertEqual(display_name, "NEW") tester.find_by_name(ui, "name").perform(command.KeyClick("Enter")) + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa self.assertEqual(foo.name, "NEW") + self.assertEqual(display_name, foo.name) def test_custom_auto_set_true_update_text(self): @@ -187,7 +201,10 @@ def test_custom_auto_set_true_update_text(self): with tester.create_ui(foo, dict(view=view)) as ui: with self.assertTraitChanges(foo, "name", count=3): tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + # with auto-set the displayed name should match the name trait + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW") + self.assertEqual(display_name, foo.name) def test_custom_auto_set_false_update_text(self): @@ -198,22 +215,32 @@ def test_custom_auto_set_false_update_text(self): with tester.create_ui(foo, dict(view=view)) as ui: tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) tester.find_by_name(ui, "name").perform(command.KeyClick("Enter")) + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa self.assertEqual(foo.name, "NEW\n") + self.assertEqual(display_name, foo.name) - @unittest.skipUnless( - Version(TRAITS_VERSION) >= Version("6.1.0"), - "This test requires traits >= 6.1.0" - ) - def test_format_func_used(self): + def test_readonly_editor(self): + foo = Foo(name= "A name") + view = get_view(style="readonly", auto_set=True) + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + # Trying to type should do nothing + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + tester.find_by_name(ui, "name").perform(command.KeyClick("Space")) + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + self.assertEqual(display_name, "A name") + + + def check_format_func_used(self, style): # Regression test for enthought/traitsui#790 # The test will fail with traits < 6.1.0 because the bug # is fixed in traits, see enthought/traitsui#980 for moving those # relevant code to traitsui. foo = Foo(name="william", nickname="bill") view = View( - Item("name", format_func=lambda s: s.upper()), - Item("nickname"), + Item("name", format_func=lambda s: s.upper(), style=style), + Item("nickname", style=style), ) tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: @@ -225,3 +252,27 @@ def test_format_func_used(self): ) self.assertEqual(display_name, "WILLIAM") self.assertEqual(display_nickname, "bill") + + + @unittest.skipUnless( + Version(TRAITS_VERSION) >= Version("6.1.0"), + "This test requires traits >= 6.1.0" + ) + def test_format_func_used_simple(self): + self.check_format_func_used(style='simple') + + + @unittest.skipUnless( + Version(TRAITS_VERSION) >= Version("6.1.0"), + "This test requires traits >= 6.1.0" + ) + def test_format_func_used_custom(self): + self.check_format_func_used(style='custom') + + + @unittest.skipUnless( + Version(TRAITS_VERSION) >= Version("6.1.0"), + "This test requires traits >= 6.1.0" + ) + def test_format_func_used_readonly(self): + self.check_format_func_used(style='readonly') From 38e6ff7c0e62ec24bfda22728fc5c5e301fa46df Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 13:27:00 -0500 Subject: [PATCH 39/60] flake8 fixes --- traitsui/testing/tester/command.py | 3 ++- traitsui/testing/tester/exceptions.py | 2 ++ traitsui/testing/tester/qt4/helpers.py | 15 ++++++----- .../tester/qt4/implementation/text_editor.py | 5 ++-- .../testing/tester/qt4/tests/test_helpers.py | 18 ++++++------- .../testing/tester/tests/test_ui_wrapper.py | 2 -- .../testing/tester/wx/default_registry.py | 2 +- traitsui/testing/tester/wx/helpers.py | 21 ++++++++++------ .../tester/wx/implementation/text_editor.py | 6 ++--- .../testing/tester/wx/tests/test_helpers.py | 9 +++---- traitsui/tests/editors/test_text_editor.py | 25 ++++++------------- 11 files changed, 53 insertions(+), 55 deletions(-) diff --git a/traitsui/testing/tester/command.py b/traitsui/testing/tester/command.py index a325444c5..c9586e739 100644 --- a/traitsui/testing/tester/command.py +++ b/traitsui/testing/tester/command.py @@ -27,6 +27,7 @@ class MouseClick: """ pass + class KeySequence: """ An object representing the user typing a sequence of keys. @@ -56,4 +57,4 @@ class KeyClick: """ def __init__(self, key): - self.key = key \ No newline at end of file + self.key = key diff --git a/traitsui/testing/tester/exceptions.py b/traitsui/testing/tester/exceptions.py index 6f1ba8269..2e36da8bf 100644 --- a/traitsui/testing/tester/exceptions.py +++ b/traitsui/testing/tester/exceptions.py @@ -19,11 +19,13 @@ 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 + class InteractionNotSupported(TesterError): """ Raised when an interaction is not supported by a wrapper. diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 7014c0d68..fc636bf58 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -17,6 +17,7 @@ from traitsui.testing.tester.exceptions import Disabled from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP + def key_click(widget, key, delay=0): """ Performs a key click of the given key on the given widget after a delay. @@ -28,7 +29,7 @@ def key_click(widget, key, delay=0): key : str Standardized (pyface) name for a keyboard event. e.g. "Enter", "Tab", "Space", "0", "1", "A", ... - delay : int + delay : int Time delay (in ms) in which the key click will be performed. """ if "-" in key: @@ -60,7 +61,8 @@ def key_click(widget, key, delay=0): delay=delay, ) -## Generic Handlers ############################ +# Generic Handlers ########################################################### + def mouse_click_qwidget(control, delay): """ Performs a mouce click on a Qt widget. @@ -78,10 +80,11 @@ def mouse_click_qwidget(control, delay): delay=delay, ) + def key_sequence_qwidget(control, interaction, delay): """ Performs simulated typing of a sequence of keys on the given widget after a delay. - + Parameters ---------- control : Qwidget @@ -89,7 +92,7 @@ def key_sequence_qwidget(control, interaction, delay): interaction : instance of command.KeySequence The interaction object holding the sequence of key inputs to be simulated being typed - delay : int + delay : int Time delay (in ms) in which each key click in the sequence will be performed. """ @@ -100,7 +103,7 @@ def key_sequence_qwidget(control, interaction, delay): def key_click_qwidget(control, interaction, delay): """ Performs simulated typing of a key on the given widget after a delay. - + Parameters ---------- control : Qwidget @@ -108,7 +111,7 @@ def key_click_qwidget(control, interaction, delay): interaction : instance of command.KeyClick The interaction object holding the key input to be simulated being typed - delay : int + delay : int Time delay (in ms) in which the key click will be performed. """ if not control.isEnabled(): diff --git a/traitsui/testing/tester/qt4/implementation/text_editor.py b/traitsui/testing/tester/qt4/implementation/text_editor.py index 326098507..ea8b608df 100644 --- a/traitsui/testing/tester/qt4/implementation/text_editor.py +++ b/traitsui/testing/tester/qt4/implementation/text_editor.py @@ -30,13 +30,14 @@ def simple_DisplayedText_handler(wrapper, interaction): Notes ----- Qt SimpleEditors occassionally use QtGui.QTextEdit as their control, and - other times use QtGui.QLineEdit + other times use QtGui.QLineEdit ''' if isinstance(wrapper.target.control, QtGui.QLineEdit): return wrapper.target.control.displayText() elif isinstance(wrapper.target.control, QtGui.QTextEdit): return wrapper.target.control.toPlainText() + def register(registry): """ Register actions for the given registry. @@ -48,7 +49,7 @@ def register(registry): wrapper.target.control, interaction, wrapper.delay))), (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( wrapper.target.control, interaction, wrapper.delay))), - (command.MouseClick,(lambda wrapper, _: helpers.mouse_click_qwidget( + (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( wrapper.target.control, wrapper.delay))), ] for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: diff --git a/traitsui/testing/tester/qt4/tests/test_helpers.py b/traitsui/testing/tester/qt4/tests/test_helpers.py index 6ae71c2c9..5386cebe7 100644 --- a/traitsui/testing/tester/qt4/tests/test_helpers.py +++ b/traitsui/testing/tester/qt4/tests/test_helpers.py @@ -56,7 +56,6 @@ def test_mouse_click_disabled(self): # then self.assertEqual(click_slot.call_count, 0) - def test_key_sequence(self): # test on different Qwidget objects textboxes = [QtGui.QLineEdit(), QtGui.QTextEdit()] @@ -65,7 +64,9 @@ def test_key_sequence(self): textbox.textChanged.connect(change_slot) # when - helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) + helpers.key_sequence_qwidget(textbox, + command.KeySequence("abc"), + 0) # then if i == 0: @@ -75,22 +76,22 @@ def test_key_sequence(self): # each keystroke fires a signal self.assertEqual(change_slot.call_count, 3) - def test_key_sequence_disabled(self): textbox = QtGui.QLineEdit() textbox.setEnabled(False) - + # this will fail, because one should not be allowed to set # cursor on the widget to type anything with self.assertRaises(Disabled): - helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) - + helpers.key_sequence_qwidget(textbox, + command.KeySequence("abc"), + 0) def test_key_click(self): textbox = QtGui.QLineEdit() change_slot = mock.Mock() textbox.editingFinished.connect(change_slot) - + # sanity check on editingFinished signal helpers.key_sequence_qwidget(textbox, command.KeySequence("abc"), 0) self.assertEqual(change_slot.call_count, 0) @@ -107,15 +108,12 @@ def test_key_click(self): self.assertEqual(change_slot.call_count, 1) self.assertEqual(textbox.toPlainText(), "\n") - def test_key_click_disabled(self): textbox = QtGui.QLineEdit() textbox.setEnabled(False) change_slot = mock.Mock() textbox.editingFinished.connect(change_slot) - with self.assertRaises(Disabled): helpers.key_click_qwidget(textbox, command.KeyClick("Enter"), 0) self.assertEqual(change_slot.call_count, 0) - diff --git a/traitsui/testing/tester/tests/test_ui_wrapper.py b/traitsui/testing/tester/tests/test_ui_wrapper.py index 33d32b452..4f136213d 100644 --- a/traitsui/testing/tester/tests/test_ui_wrapper.py +++ b/traitsui/testing/tester/tests/test_ui_wrapper.py @@ -23,8 +23,6 @@ InteractionNotSupported, LocationNotSupported, ) -from traitsui.testing.tester.registry import TargetRegistry -from traitsui.testing.tester import locator from traitsui.testing.tester.ui_wrapper import ( UIWrapper, ) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 8ce4465b7..909789f71 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -32,5 +32,5 @@ def get_default_registry(): # TextEditor text_editor.register(registry) - + return registry diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index bf672a31c..c43d19855 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -35,7 +35,7 @@ def key_click(widget, key, delay=0): key : str Standardized (pyface) name for a keyboard event. e.g. "Enter", "Tab", "Space", "0", "1", "A", ... - delay : int + delay : int Time delay (in ms) in which the key click will be performed. """ if "-" in key: @@ -50,7 +50,7 @@ def key_click(widget, key, delay=0): KEY = ord(key) except: raise ValueError( - "Unknown key {!r}. Expected one of these: {!r}, or a unicode character".format( + "Unknown key {!r}. Expected one of these: {!r}, or a unicode character".format( # noqa key, sorted(mapping) )) else: @@ -59,6 +59,11 @@ def key_click(widget, key, delay=0): apply_modifiers(key_event, modifiers) key_event.SetUnicodeKey(KEY) widget.EmulateKeyPress(key_event) + else: + raise ValueError( + "Unknown key {!r}. Expected one of these: {!r}, or a unicode character".format( # noqa + key, sorted(mapping) + )) else: wx.MilliSleep(delay) @@ -75,9 +80,9 @@ def mouse_click_button(control, delay): Parameters ---------- control : wxObject - The wx Object to be clicked. + The wx Object to be clicked. delay: int - Time delay (in ms) in which click will be performed. + Time delay (in ms) in which click will be performed. """ if not control.IsEnabled(): return @@ -91,7 +96,7 @@ def mouse_click_button(control, delay): def key_click_text_ctrl(control, interaction, delay): """ Performs simulated typing of a key on the given wxObject after a delay. - + Parameters ---------- control : wxObject @@ -99,7 +104,7 @@ def key_click_text_ctrl(control, interaction, delay): interaction : instance of command.KeyClick The interaction object holding the key input to be simulated being typed - delay : int + delay : int Time delay (in ms) in which the key click will be performed. """ if not control.IsEditable(): @@ -112,7 +117,7 @@ def key_click_text_ctrl(control, interaction, delay): def key_sequence_text_ctrl(control, interaction, delay): """ Performs simulated typing of a sequence of keys on the given wxObject after a delay. - + Parameters ---------- control : wxObject @@ -120,7 +125,7 @@ def key_sequence_text_ctrl(control, interaction, delay): interaction : instance of command.KeySequence The interaction object holding the sequence of key inputs to be simulated being typed - delay : int + delay : int Time delay (in ms) in which each key click in the sequence will be performed. """ diff --git a/traitsui/testing/tester/wx/implementation/text_editor.py b/traitsui/testing/tester/wx/implementation/text_editor.py index 71f053e92..60c2014ef 100644 --- a/traitsui/testing/tester/wx/implementation/text_editor.py +++ b/traitsui/testing/tester/wx/implementation/text_editor.py @@ -31,7 +31,7 @@ def readonly_DisplayedText_handler(wrapper, interaction): Notes ----- wx Readonly Editors occassionally use wx.TextCtrl as their control, and - other times use wx.StaticText. + other times use wx.StaticText. ''' if isinstance(wrapper.target.control, wx.TextCtrl): return wrapper.target.control.GetValue() @@ -40,11 +40,11 @@ def readonly_DisplayedText_handler(wrapper, interaction): def register(registry): - + handlers = [ (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_text_ctrl( wrapper.target.control, interaction, wrapper.delay))), - (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( + (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( wrapper.target.control, interaction, wrapper.delay))), (query.DisplayedText, lambda wrapper, _: wrapper.target.control.GetValue()) ] diff --git a/traitsui/testing/tester/wx/tests/test_helpers.py b/traitsui/testing/tester/wx/tests/test_helpers.py index 5709598c8..4de7cf3b7 100644 --- a/traitsui/testing/tester/wx/tests/test_helpers.py +++ b/traitsui/testing/tester/wx/tests/test_helpers.py @@ -12,7 +12,7 @@ import unittest from unittest import mock -from traitsui.testing.tester import command, query +from traitsui.testing.tester import command from traitsui.testing.tester.exceptions import Disabled from traitsui.testing.tester.wx import helpers @@ -64,7 +64,6 @@ def test_mouse_click_disabled_button(self): # then self.assertEqual(handler.call_count, 0) - def test_key_sequence(self): textbox = wx.TextCtrl(self.frame) @@ -77,8 +76,9 @@ def test_key_sequence_disabled(self): textbox.SetEditable(False) with self.assertRaises(Disabled): - helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0) - + helpers.key_sequence_text_ctrl(textbox, + command.KeySequence("abc"), + 0) def test_key_click(self): textbox = wx.TextCtrl(self.frame) @@ -88,7 +88,6 @@ def test_key_click(self): helpers.key_click_text_ctrl(textbox, command.KeyClick("Backspace"), 0) self.assertEqual(textbox.Value, "") - def test_key_click_disabled(self): textbox = wx.TextCtrl(self.frame) textbox.SetEditable(False) diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index 64c4194b2..332869731 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -76,7 +76,6 @@ def test_text_editor_placeholder_text(self): "Enter name", ) - def test_text_editor_placeholder_text_and_readonly(self): # Placeholder can be set independently of read_only flag foo = Foo() @@ -139,7 +138,7 @@ def check_editor_init_and_dispose(self, style, auto_set): # Smoke test to test setup and tear down of an editor. foo = Foo() view = get_view(style=style, auto_set=auto_set) - with UITester().create_ui(foo, dict(view=view)) as ui: + with UITester().create_ui(foo, dict(view=view)): pass def test_simple_editor_init_and_dispose(self): @@ -168,13 +167,12 @@ def test_simple_auto_set_update_text(self): tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: with self.assertTraitChanges(foo, "name", count=3): - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) # noqa # with auto-set the displayed name should match the name trait - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) - def test_simple_auto_set_false_do_not_update(self): foo = Foo(name="") view = get_view(style="simple", auto_set=False) @@ -192,7 +190,6 @@ def test_simple_auto_set_false_do_not_update(self): self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) - def test_custom_auto_set_true_update_text(self): # the auto_set flag is disregard for custom editor. foo = Foo() @@ -200,13 +197,12 @@ def test_custom_auto_set_true_update_text(self): tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: with self.assertTraitChanges(foo, "name", count=3): - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) # noqa # with auto-set the displayed name should match the name trait - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) - def test_custom_auto_set_false_update_text(self): # the auto_set flag is disregard for custom editor. foo = Foo() @@ -219,19 +215,17 @@ def test_custom_auto_set_false_update_text(self): self.assertEqual(foo.name, "NEW\n") self.assertEqual(display_name, foo.name) - def test_readonly_editor(self): - foo = Foo(name= "A name") + foo = Foo(name="A name") view = get_view(style="readonly", auto_set=True) tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: - # Trying to type should do nothing + # Trying to type should do nothing tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) tester.find_by_name(ui, "name").perform(command.KeyClick("Space")) display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa self.assertEqual(display_name, "A name") - def check_format_func_used(self, style): # Regression test for enthought/traitsui#790 # The test will fail with traits < 6.1.0 because the bug @@ -248,12 +242,11 @@ def check_format_func_used(self, style): tester.find_by_name(ui, "name").inspect(query.DisplayedText()) ) display_nickname = ( - tester.find_by_name(ui, "nickname").inspect(query.DisplayedText()) + tester.find_by_name(ui, "nickname").inspect(query.DisplayedText()) # noqa ) self.assertEqual(display_name, "WILLIAM") self.assertEqual(display_nickname, "bill") - @unittest.skipUnless( Version(TRAITS_VERSION) >= Version("6.1.0"), "This test requires traits >= 6.1.0" @@ -261,7 +254,6 @@ def check_format_func_used(self, style): def test_format_func_used_simple(self): self.check_format_func_used(style='simple') - @unittest.skipUnless( Version(TRAITS_VERSION) >= Version("6.1.0"), "This test requires traits >= 6.1.0" @@ -269,7 +261,6 @@ def test_format_func_used_simple(self): def test_format_func_used_custom(self): self.check_format_func_used(style='custom') - @unittest.skipUnless( Version(TRAITS_VERSION) >= Version("6.1.0"), "This test requires traits >= 6.1.0" From 1189ddc1f3694d1d0eb7772ff27a7c519a648fc9 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 13:55:56 -0500 Subject: [PATCH 40/60] adding tests using QLabel object (nothing should happen when trying to type) --- traitsui/testing/tester/qt4/tests/test_helpers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/traitsui/testing/tester/qt4/tests/test_helpers.py b/traitsui/testing/tester/qt4/tests/test_helpers.py index 5386cebe7..24abd2c7a 100644 --- a/traitsui/testing/tester/qt4/tests/test_helpers.py +++ b/traitsui/testing/tester/qt4/tests/test_helpers.py @@ -21,7 +21,6 @@ from traitsui.testing.tester.exceptions import Disabled from traitsui.testing.tester.qt4 import helpers - try: from pyface.qt import QtGui except ImportError: @@ -76,6 +75,13 @@ def test_key_sequence(self): # each keystroke fires a signal self.assertEqual(change_slot.call_count, 3) + # for a QLabel, one can try a key sequence and nothing will happen + textbox = QtGui.QLabel() + helpers.key_sequence_qwidget(textbox, + command.KeySequence("abc"), + 0) + self.assertEqual(textbox.text(), "") + def test_key_sequence_disabled(self): textbox = QtGui.QLineEdit() textbox.setEnabled(False) @@ -108,6 +114,11 @@ def test_key_click(self): self.assertEqual(change_slot.call_count, 1) self.assertEqual(textbox.toPlainText(), "\n") + # for a QLabel, one can try a key click and nothing will happen + textbox = QtGui.QLabel() + helpers.key_click_qwidget(textbox, command.KeyClick("A"), 0) + self.assertEqual(textbox.text(), "") + def test_key_click_disabled(self): textbox = QtGui.QLineEdit() textbox.setEnabled(False) From 7ba111e799f5aeb8f8db620ad2b7ffaf84d121c9 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 26 Aug 2020 16:06:51 -0500 Subject: [PATCH 41/60] adding range_editor implementation, and readding get___object_registry functions --- traitsui/testing/tester/locator.py | 9 +++ .../testing/tester/qt4/default_registry.py | 8 ++- traitsui/testing/tester/qt4/helpers.py | 56 +++++++++++++++++++ .../tester/qt4/implementation/range_editor.py | 40 +++++++++++++ .../testing/tester/wx/default_registry.py | 8 ++- traitsui/testing/tester/wx/helpers.py | 46 +++++++++++++++ .../tester/wx/implementation/range_editor.py | 39 +++++++++++++ 7 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 traitsui/testing/tester/qt4/implementation/range_editor.py create mode 100644 traitsui/testing/tester/wx/implementation/range_editor.py diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index d4e0b36b1..51aa83e6c 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -17,6 +17,8 @@ applied. """ +import enum + class NestedUI: """ A locator for locating a nested ``traitsui.ui.UI`` object assuming @@ -35,3 +37,10 @@ class TargetByName: """ def __init__(self, name): self.name = name + + +class WidgetType(enum.Enum): + """ An Enum of widget types. + """ + + textbox = "textbox" diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 9084a3bcd..77d3e4b84 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -9,9 +9,10 @@ # Thanks for using Enthought open source! # -from traitsui.testing.tester.registry import TargetRegistry +from traitsui.testing.tester.qt4.helpers import get_qobject_registry from traitsui.testing.tester.qt4.implementation import ( button_editor, + range_editor, text_editor, ) @@ -25,7 +26,7 @@ def get_default_registry(): The default registry containing implementations for TraitsUI editors that is qt specific. """ - registry = TargetRegistry() + registry = get_qobject_registry() # ButtonEditor button_editor.register(registry) @@ -33,4 +34,7 @@ def get_default_registry(): # TextEditor text_editor.register(registry) + # RangeEditor + range_editor.register(registry) + return registry diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index fc636bf58..d0614d6b9 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -12,9 +12,12 @@ from functools import reduce from pyface.qt import QtCore +from pyface.qt import QtGui from pyface.qt.QtTest import QTest +from traitsui.testing.tester import command, query from traitsui.testing.tester.exceptions import Disabled +from traitsui.testing.tester.registry import TargetRegistry from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP @@ -117,3 +120,56 @@ def key_click_qwidget(control, interaction, delay): if not control.isEnabled(): raise Disabled("{!r} is disabled.".format(control)) key_click(control, interaction.key, delay=delay) + + +def get_qobject_registry(): + """ Creates a generic registry for handling/solving qt objects. (i.e. + this registry is independent of TraitsUI) + + Returns + ------- + registry : TargetRegistry + Registry containing qt specific generic handlers and solvers. + """ + registry = TargetRegistry() + + widget_classes = [ + QtGui.QLineEdit, + QtGui.QTextEdit, + QtGui.QPushButton, + ] + handlers = [ + (command.KeySequence, (lambda wrapper, interaction: key_sequence_qwidget( + wrapper.target, interaction.sequence, wrapper.delay))), + (command.KeyClick, (lambda wrapper, interaction: key_click_qwidget( + wrapper.target, interaction.key, wrapper.delay))), + (command.MouseClick, (lambda wrapper, _: mouse_click_qwidget( + wrapper.target, wrapper.delay))), + ] + for widget_class in widget_classes: + for interaction_class, handler in handlers: + registry.register_handler( + target_class=widget_class, + interaction_class=interaction_class, + handler=handler, + ) + + registry.register_handler( + target_class=QtGui.QLineEdit, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.displayText(), + ) + + registry.register_handler( + target_class=QtGui.QTextEdit, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.toPlainText(), + ) + + registry.register_handler( + target_class=QtGui.QPushButton, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.text(), + ) + + return registry \ No newline at end of file diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py new file mode 100644 index 000000000..1a97d0858 --- /dev/null +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -0,0 +1,40 @@ +# 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.range_editor import ( + LargeRangeSliderEditor, + LogRangeSliderEditor, + RangeTextEditor, + SimpleSliderEditor, + SimpleSpinEditor, +) + +from traitsui.testing.tester import locator + +def resolve_location_simple_slider(wrapper, location): + if location == locator.WidgetType.textbox: + return wrapper.editor.control.text + + raise NotImplementedError() + + +def register(registry): + + targets = [SimpleSliderEditor, + LogRangeSliderEditor, + LargeRangeSliderEditor, + RangeTextEditor] + for target_class in targets: + registry.register_solver( + target_class=target_class, + locator_class=locator.WidgetType, + solver=resolve_location_simple_slider, + ) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 909789f71..4e0cc6312 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -9,9 +9,10 @@ # Thanks for using Enthought open source! # -from traitsui.testing.tester.registry import TargetRegistry +from traitsui.testing.tester.wx.helpers import get_wobject_registry from traitsui.testing.tester.wx.implementation import ( button_editor, + range_editor, text_editor, ) @@ -25,7 +26,7 @@ def get_default_registry(): The default registry containing implementations for TraitsUI editors that is wx specific. """ - registry = TargetRegistry() + registry = get_wobject_registry() # ButtonEditor button_editor.register(registry) @@ -33,4 +34,7 @@ def get_default_registry(): # TextEditor text_editor.register(registry) + # RangeEditor + range_editor.register(registry) + return registry diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index c43d19855..3d9004f97 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -11,7 +11,9 @@ import wx +from traitsui.testing.tester import command, query from traitsui.testing.tester.exceptions import Disabled +from traitsui.testing.tester.registry import TargetRegistry from traitsui.wx.key_event_to_name import key_map as _KEY_MAP @@ -135,3 +137,47 @@ def key_sequence_text_ctrl(control, interaction, delay): control.SetFocus() for char in interaction.sequence: key_click(control, char, delay) + + +def get_wobject_registry(): + """ Creates a generic registry for handling/solving wx objects. (i.e. + this registry is independent of TraitsUI) + Returns + ------- + registry : TargetRegistry + Registry containing wx specific generic handlers and solvers. + """ + registry = TargetRegistry() + + + handlers = [ + (command.KeyClick, (lambda wrapper, interaction: key_click_text_ctrl( + wrapper.target, interaction.key, wrapper.delay))), + (command.KeySequence, (lambda wrapper, interaction: key_sequence_text_ctrl( + wrapper.target, interaction.sequence, wrapper.delay))), + (query.DisplayedText, lambda wrapper, _: wrapper.target.GetValue()), + ] + + for interaction_class, handler in handlers: + registry.register_handler( + target_class=wx.TextCtrl, + interaction_class=interaction_class, + handler=handler + ) + + registry.register_handler( + target_class=wx.StaticText, + interaction_class=query.DisplayedText, + handler=lambda wrapper, action: ( + wrapper.target.GetLabel() + ), + ) + + registry.register_handler( + target_class=wx.Button, + interaction_class=command.MouseClick, + handler=lambda wrapper, _: mouse_click_button(wrapper.target, wrapper.delay) + ) + + return registry + \ No newline at end of file diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py new file mode 100644 index 000000000..9e023174e --- /dev/null +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -0,0 +1,39 @@ +# 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.range_editor import ( + LargeRangeSliderEditor, + LogRangeSliderEditor, + RangeTextEditor, + SimpleSliderEditor, + SimpleSpinEditor, +) + +from traitsui.testing.tester import locator + +def resolve_location_simple_slider(wrapper, location): + if location == locator.WidgetType.textbox: + return wrapper.editor.control.text + + raise NotImplementedError() + + +def register(registry): + + targets = [SimpleSliderEditor, + LogRangeSliderEditor, + LargeRangeSliderEditor, + RangeTextEditor] + for target_class in targets: + registry.register_solver( + target_class=target_class, + locator_class=locator.WidgetType, + solver=resolve_location_simple_slider, + ) From f4f2be2ad03b2cfe4095007278affbc6ae7e6127 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 27 Aug 2020 10:59:51 -0500 Subject: [PATCH 42/60] start of refactor for located basic object handlers --- .../tester/qt4/implementation/range_editor.py | 30 +++++++++++++++++-- .../tests/editors/test_range_editor_text.py | 20 +++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index 1a97d0858..494060001 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -17,11 +17,36 @@ SimpleSpinEditor, ) -from traitsui.testing.tester import locator +from traitsui.testing.tester import command, locator, query +from traitsui.testing.tester.qt4 import helpers + + +class RangeEditorTextbox: + def __init__(self, textbox): + self.textbox = textbox + + @classmethod + def register(cls, registry): + handlers = [ + (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( + wrapper.target.textbox, interaction.sequence, wrapper.delay))), + (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( + wrapper.target.textbox, interaction.key, wrapper.delay))), + (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( + wrapper.target.textbox, wrapper.delay))), + (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.displayText()), + ] + for interaction_class, handler in handlers: + registry.register_handler( + target_class=cls, + interaction_class=interaction_class, + handler=handler, + ) + def resolve_location_simple_slider(wrapper, location): if location == locator.WidgetType.textbox: - return wrapper.editor.control.text + return RangeEditorTextbox(textbox=wrapper.editor.control.text) raise NotImplementedError() @@ -38,3 +63,4 @@ def register(registry): locator_class=locator.WidgetType, solver=resolve_location_simple_slider, ) + RangeEditorTextbox.register(registry) diff --git a/traitsui/tests/editors/test_range_editor_text.py b/traitsui/tests/editors/test_range_editor_text.py index f9a3fa101..546d8c672 100644 --- a/traitsui/tests/editors/test_range_editor_text.py +++ b/traitsui/tests/editors/test_range_editor_text.py @@ -26,6 +26,8 @@ from traitsui.view import View from traitsui.editors.range_editor import RangeEditor +from traitsui.testing.tester import command, locator, query +from traitsui.testing.tester.ui_tester import UITester from traitsui.tests._tools import ( create_ui, press_ok_button, @@ -68,7 +70,18 @@ def test_wx_text_editing(self): # (tests a bug where this fails with an AttributeError) num = NumberWithRangeEditor() - with reraise_exceptions(), create_ui(num) as ui: + tester = UITester() + with tester.create_ui(num) as ui: + # the following is equivalent to setting the text in the text + # control, then pressing OK + text = tester.find_by_name(ui, "number").locate(locator.WidgetType.textbox) + text.perform(command.KeyClick("1")) + text.perform(command.KeyClick("Enter")) + + # the number traits should be between 3 and 8 + self.assertTrue(3 <= num.number <= 8) + + """ with reraise_exceptions(), create_ui(num) as ui: # the following is equivalent to setting the text in the text # control, then pressing OK @@ -77,7 +90,7 @@ def test_wx_text_editing(self): textctrl.SetValue("1") # the number traits should be between 3 and 8 - self.assertTrue(3 <= num.number <= 8) + self.assertTrue(3 <= num.number <= 8) """ @requires_toolkit([ToolkitName.qt]) def test_avoid_slider_feedback(self): @@ -86,7 +99,8 @@ def test_avoid_slider_feedback(self): from pyface import qt num = FloatWithRangeEditor() - with reraise_exceptions(), create_ui(num) as ui: + tester = UITester() + with tester.create_ui(num) as ui: # the following is equivalent to setting the text in the text # control, then pressing OK From 40ad90a40d6e39b28453696e8fb2ea2d45d5777f Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 27 Aug 2020 11:38:04 -0500 Subject: [PATCH 43/60] improving tests, and adding mouse click on TextEditors for wx --- traitsui/testing/tester/wx/helpers.py | 30 +++++++- .../tester/wx/implementation/text_editor.py | 19 +++-- traitsui/tests/editors/test_text_editor.py | 72 +++++++++++++++++-- 3 files changed, 110 insertions(+), 11 deletions(-) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index c43d19855..60138050c 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -92,6 +92,26 @@ def mouse_click_button(control, delay): ) control.ProcessEvent(click_event) +def mouse_click_text_ctrl(control, delay): + """ Performs a mouce click on a wxTextCtrl. + + Parameters + ---------- + control : wxObject + The wx Object to be clicked. + delay: int + Time delay (in ms) in which click will be performed. + """ + if not control.IsEnabled(): + return + if not control.HasFocus(): + control.SetFocus() + wx.MilliSleep(delay) + click_event = wx.CommandEvent( + wx.wxEVT_COMMAND_LEFT_CLICK, control.GetId() + ) + control.ProcessEvent(click_event) + def key_click_text_ctrl(control, interaction, delay): """ Performs simulated typing of a key on the given wxObject @@ -100,13 +120,16 @@ def key_click_text_ctrl(control, interaction, delay): Parameters ---------- control : wxObject - The wx Object to be acted on. Should be wx.TextCtrl instance + The wx Object to be acted on. Should be wx.TextCtrl instance. If it is + a wx.StaticText (i.e. from a ReadonlyEditor) nothing occurs. interaction : instance of command.KeyClick The interaction object holding the key input to be simulated being typed delay : int Time delay (in ms) in which the key click will be performed. """ + if isinstance(control, wx.StaticText): + return if not control.IsEditable(): raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): @@ -121,7 +144,8 @@ def key_sequence_text_ctrl(control, interaction, delay): Parameters ---------- control : wxObject - The wx Object to be acted on. Should be wx.TextCtrl instance + The wx Object to be acted on. Should be wx.TextCtrl instance. If it is + a wx.StaticText (i.e. from a ReadonlyEditor) nothing occurs. interaction : instance of command.KeySequence The interaction object holding the sequence of key inputs to be simulated being typed @@ -129,6 +153,8 @@ def key_sequence_text_ctrl(control, interaction, delay): Time delay (in ms) in which each key click in the sequence will be performed. """ + if isinstance(control, wx.StaticText): + return if not control.IsEditable(): raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): diff --git a/traitsui/testing/tester/wx/implementation/text_editor.py b/traitsui/testing/tester/wx/implementation/text_editor.py index 60c2014ef..ad2cd3508 100644 --- a/traitsui/testing/tester/wx/implementation/text_editor.py +++ b/traitsui/testing/tester/wx/implementation/text_editor.py @@ -36,7 +36,7 @@ def readonly_DisplayedText_handler(wrapper, interaction): if isinstance(wrapper.target.control, wx.TextCtrl): return wrapper.target.control.GetValue() elif isinstance(wrapper.target.control, wx.StaticText): - return wrapper.target.control.label + return wrapper.target.control.GetLabel() def register(registry): @@ -46,19 +46,28 @@ def register(registry): wrapper.target.control, interaction, wrapper.delay))), (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( wrapper.target.control, interaction, wrapper.delay))), - (query.DisplayedText, lambda wrapper, _: wrapper.target.control.GetValue()) + (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_text_ctrl( + wrapper.target.control, wrapper.delay))), ] - for target_class in [CustomEditor, SimpleEditor]: + for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: for interaction_class, handler in handlers: registry.register_handler( target_class=target_class, interaction_class=interaction_class, - solver=handler, + handler=handler, ) + for target_class in [CustomEditor, SimpleEditor]: + registry.register_handler( + target_class=target_class, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: wrapper.target.control.GetValue(), + ) + + registry.register_handler( target_class=ReadonlyEditor, interaction_class=query.DisplayedText, - solver=readonly_DisplayedText_handler, + handler=readonly_DisplayedText_handler, ) diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index 332869731..af43a56e9 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -124,7 +124,7 @@ def test_text_editor_custom_style_placeholder(self): # We should be able to run this test case against wx. # Not running them now to avoid test interaction. See enthought/traitsui#752 -@requires_toolkit([ToolkitName.qt]) +@requires_toolkit([ToolkitName.qt, ToolkitName.wx]) class TestTextEditor(unittest.TestCase, UnittestTools): """ Tests that can be run with any toolkit as long as there is an implementation for simulating user interactions. @@ -173,7 +173,11 @@ def test_simple_auto_set_update_text(self): self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) - def test_simple_auto_set_false_do_not_update(self): + # Currently if auto_set is false, and enter_set is false (the default + # behavior), on Qt to ensure the text is actually set, Enter will set + # the value + @requires_toolkit([ToolkitName.qt]) + def test_simple_auto_set_false_do_not_update_qt(self): foo = Foo(name="") view = get_view(style="simple", auto_set=False) tester = UITester() @@ -190,8 +194,37 @@ def test_simple_auto_set_false_do_not_update(self): self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) + # If auto_set is false, the value can be set by killing the focus. This + # can be done by simply moving to another textbox. + @requires_toolkit([ToolkitName.wx]) + def test_simple_auto_set_false_do_not_update_wx(self): + foo = Foo(name="") + view = View( + Item("name", + editor=TextEditor(auto_set=False), + style="simple" + ), + Item("nickname", + editor=TextEditor(auto_set=False), + style="simple" + ) + ) + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + # with auto-set as False the displayed name should match what has + # been typed not the trait itself, After moving to another textbox it + # should match the name trait + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + self.assertEqual(foo.name, "") + self.assertEqual(display_name, "NEW") + tester.find_by_name(ui, "nickname").perform(command.MouseClick()) + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + self.assertEqual(foo.name, "NEW") + self.assertEqual(display_name, foo.name) + def test_custom_auto_set_true_update_text(self): - # the auto_set flag is disregard for custom editor. + # the auto_set flag is disregard for custom editor. (not true on WX) foo = Foo() view = get_view(auto_set=True, style="custom") tester = UITester() @@ -203,8 +236,10 @@ def test_custom_auto_set_true_update_text(self): self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) + + @requires_toolkit([ToolkitName.qt]) def test_custom_auto_set_false_update_text(self): - # the auto_set flag is disregard for custom editor. + # the auto_set flag is disregard for custom editor. (not true on WX) foo = Foo() view = get_view(auto_set=False, style="custom") tester = UITester() @@ -215,6 +250,35 @@ def test_custom_auto_set_false_update_text(self): self.assertEqual(foo.name, "NEW\n") self.assertEqual(display_name, foo.name) + # If auto_set is false, the value can be set by killing the focus. This + # can be done by simply moving to another textbox. + @requires_toolkit([ToolkitName.wx]) + def test_custom_auto_set_false_do_not_update_wx(self): + foo = Foo(name="") + view = View( + Item("name", + editor=TextEditor(auto_set=False), + style="custom" + ), + Item("nickname", + editor=TextEditor(auto_set=False), + style="custom" + ) + ) + tester = UITester() + with tester.create_ui(foo, dict(view=view)) as ui: + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + # with auto-set as False the displayed name should match what has + # been typed not the trait itself, After moving to another textbox it + # should match the name trait + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + self.assertEqual(foo.name, "") + self.assertEqual(display_name, "NEW") + tester.find_by_name(ui, "nickname").perform(command.MouseClick()) + display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + self.assertEqual(foo.name, "NEW") + self.assertEqual(display_name, foo.name) + def test_readonly_editor(self): foo = Foo(name="A name") view = get_view(style="readonly", auto_set=True) From 705a820e449400b7edd83ab384488fd992b22038 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 27 Aug 2020 13:40:45 -0500 Subject: [PATCH 44/60] making suggested changes --- traitsui/testing/tester/exceptions.py | 7 +- traitsui/testing/tester/qt4/helpers.py | 45 ++++++++----- .../tester/qt4/implementation/text_editor.py | 55 ++++----------- traitsui/testing/tester/wx/helpers.py | 67 ++++++------------- .../tester/wx/implementation/text_editor.py | 21 ++++-- traitsui/tests/editors/test_text_editor.py | 7 +- 6 files changed, 85 insertions(+), 117 deletions(-) diff --git a/traitsui/testing/tester/exceptions.py b/traitsui/testing/tester/exceptions.py index 2e36da8bf..d8537faa2 100644 --- a/traitsui/testing/tester/exceptions.py +++ b/traitsui/testing/tester/exceptions.py @@ -15,12 +15,7 @@ class TesterError(Exception): pass -class SimulationError(Exception): - """ Raised when simulating user interactions on GUI.""" - pass - - -class Disabled(SimulationError): +class Disabled(TesterError): """ Raised when a simulation fails because the widget is disabled. """ pass diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index fc636bf58..2bdd44b30 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -11,7 +11,7 @@ from functools import reduce -from pyface.qt import QtCore +from pyface.qt import QtCore, QtGui from pyface.qt.QtTest import QTest from traitsui.testing.tester.exceptions import Disabled @@ -29,24 +29,10 @@ def key_click(widget, key, delay=0): key : str Standardized (pyface) name for a keyboard event. e.g. "Enter", "Tab", "Space", "0", "1", "A", ... + Note: modifiers (e.g. Shift, Alt, etc. are not currently supported) delay : int Time delay (in ms) in which the key click will be performed. """ - if "-" in key: - *modifiers, key = key.split("-") - else: - modifiers = [] - - modifier_to_qt = { - "Ctrl": QtCore.Qt.ControlModifier, - "Alt": QtCore.Qt.AltModifier, - "Meta": QtCore.Qt.MetaModifier, - "Shift": QtCore.Qt.ShiftModifier, - } - qt_modifiers = [modifier_to_qt[modifier] for modifier in modifiers] - qt_modifier = reduce( - lambda x, y: x | y, qt_modifiers, QtCore.Qt.NoModifier - ) mapping = {name: event for event, name in _KEY_MAP.items()} if key not in mapping: @@ -57,13 +43,38 @@ def key_click(widget, key, delay=0): QTest.keyClick( widget, mapping[key], - qt_modifier, + QtCore.Qt.NoModifier, delay=delay, ) + # Generic Handlers ########################################################### +def displayed_text_qobject(widget): + ''' Helper function to define handlers for various Qwidgets to handle + query.DisplayedText interactions. + + Parameters + ---------- + widget : Qwidget + The Qwidget object with text to be displayed. Should be one of the + following QWidgets: 1) QtGui.QLineEdit 2) QtGui.QTextEdit or + 3) QtGui.QLabel + + Notes + ----- + Qt SimpleEditors occassionally use QtGui.QTextEdit as their control, and + other times use QtGui.QLineEdit + ''' + if isinstance(widget, QtGui.QLineEdit): + return widget.displayText() + elif isinstance(widget, QtGui.QTextEdit): + return widget.toPlainText() + else: + return widget.text() + + def mouse_click_qwidget(control, delay): """ Performs a mouce click on a Qt widget. diff --git a/traitsui/testing/tester/qt4/implementation/text_editor.py b/traitsui/testing/tester/qt4/implementation/text_editor.py index ea8b608df..dfdf23554 100644 --- a/traitsui/testing/tester/qt4/implementation/text_editor.py +++ b/traitsui/testing/tester/qt4/implementation/text_editor.py @@ -8,40 +8,21 @@ # # Thanks for using Enthought open source! # -from pyface.qt import QtGui from traitsui.testing.tester import command, query from traitsui.testing.tester.qt4 import helpers from traitsui.qt4.text_editor import CustomEditor, ReadonlyEditor, SimpleEditor -def simple_DisplayedText_handler(wrapper, interaction): - ''' Handler for SimpleEditor to handle query.DisplayedText interactions. - - Parameters - ---------- - wrapper : UIWrapper - the UIWrapper object wrapping the SimpleEditor - interaction : Instance of query.DisplayedText - This arguiment is not used by this function. It is included so that - the function matches the standard format for a handler. The intended - interaction should always be query.DisplayedText - - Notes - ----- - Qt SimpleEditors occassionally use QtGui.QTextEdit as their control, and - other times use QtGui.QLineEdit - ''' - if isinstance(wrapper.target.control, QtGui.QLineEdit): - return wrapper.target.control.displayText() - elif isinstance(wrapper.target.control, QtGui.QTextEdit): - return wrapper.target.control.toPlainText() - - def register(registry): - """ Register actions for the given registry. + """ Register interactions for the given registry. If there are any conflicts, an error will occur. + + Parameters + ---------- + registry : TargetRegistry + The registry being registered to. """ handlers = [ @@ -52,7 +33,7 @@ def register(registry): (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( wrapper.target.control, wrapper.delay))), ] - for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: + for target_class in [CustomEditor, SimpleEditor]: for interaction_class, handler in handlers: registry.register_handler( target_class=target_class, @@ -60,18 +41,10 @@ def register(registry): handler=handler, ) - registry.register_handler( - target_class=CustomEditor, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.control.toPlainText(), - ) - registry.register_handler( - target_class=SimpleEditor, - interaction_class=query.DisplayedText, - handler=simple_DisplayedText_handler, - ) - registry.register_handler( - target_class=ReadonlyEditor, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.control.text(), - ) + for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: + registry.register_handler( + target_class=target_class, + interaction_class=query.DisplayedText, + handler=lambda wrapper, _: helpers.displayed_text_qobject( + wrapper.target.control), + ) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 60138050c..6ba2ce4da 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -15,61 +15,40 @@ from traitsui.wx.key_event_to_name import key_map as _KEY_MAP -def apply_modifiers(key_event, modifiers): - possible_modifiers = ["Alt", "Ctrl", "Meta", "Shift"] - modifier_map = {mod: (mod in modifiers) for mod in possible_modifiers} - key_event.SetAltDown(modifier_map["Alt"]) - key_event.SetControlDown(modifier_map["Ctrl"]) - key_event.SetMetaDown(modifier_map["Meta"]) - key_event.SetShiftDown(modifier_map["Shift"]) - - def key_click(widget, key, delay=0): """ Performs a key click of the given key on the given widget after a delay. Parameters ---------- - widget : wxObject - The wx Object to be clicked. Should be wx.TextCtrl instance + widget : wx.TextCtrl + The wx Object to be key cliecked to. key : str Standardized (pyface) name for a keyboard event. e.g. "Enter", "Tab", "Space", "0", "1", "A", ... + Note: modifiers (e.g. Shift, Alt, etc. are not currently supported) delay : int Time delay (in ms) in which the key click will be performed. """ - if "-" in key: - *modifiers, key = key.split("-") - else: - modifiers = [] mapping = {name: event for event, name in _KEY_MAP.items()} if key not in mapping: - if len(key) == 1: - try: - KEY = ord(key) - except: - raise ValueError( - "Unknown key {!r}. Expected one of these: {!r}, or a unicode character".format( # noqa - key, sorted(mapping) - )) - else: - wx.MilliSleep(delay) - key_event = wx.KeyEvent(wx.wxEVT_CHAR) - apply_modifiers(key_event, modifiers) - key_event.SetUnicodeKey(KEY) - widget.EmulateKeyPress(key_event) - else: + try: + KEY = ord(key) + except [TypeError, ValueError]: raise ValueError( - "Unknown key {!r}. Expected one of these: {!r}, or a unicode character".format( # noqa - key, sorted(mapping) - )) - + "Unknown key {!r}. Expected one of these: {!r}, or a unicode character".format( # noqa + key, sorted(mapping) + )) + else: + wx.MilliSleep(delay) + key_event = wx.KeyEvent(wx.wxEVT_CHAR) + key_event.SetUnicodeKey(KEY) + widget.EmulateKeyPress(key_event) else: wx.MilliSleep(delay) KEY = mapping[key] key_event = wx.KeyEvent(wx.wxEVT_CHAR) - apply_modifiers(key_event, modifiers) key_event.SetKeyCode(mapping[key]) widget.EmulateKeyPress(key_event) @@ -79,7 +58,7 @@ def mouse_click_button(control, delay): Parameters ---------- - control : wxObject + control : wxButton The wx Object to be clicked. delay: int Time delay (in ms) in which click will be performed. @@ -92,7 +71,7 @@ def mouse_click_button(control, delay): ) control.ProcessEvent(click_event) -def mouse_click_text_ctrl(control, delay): +def mouse_click_object(control, delay): """ Performs a mouce click on a wxTextCtrl. Parameters @@ -119,17 +98,14 @@ def key_click_text_ctrl(control, interaction, delay): Parameters ---------- - control : wxObject - The wx Object to be acted on. Should be wx.TextCtrl instance. If it is - a wx.StaticText (i.e. from a ReadonlyEditor) nothing occurs. + control : wxTextCtrl + The wx Object to be acted on. interaction : instance of command.KeyClick The interaction object holding the key input to be simulated being typed delay : int Time delay (in ms) in which the key click will be performed. """ - if isinstance(control, wx.StaticText): - return if not control.IsEditable(): raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): @@ -143,9 +119,8 @@ def key_sequence_text_ctrl(control, interaction, delay): Parameters ---------- - control : wxObject - The wx Object to be acted on. Should be wx.TextCtrl instance. If it is - a wx.StaticText (i.e. from a ReadonlyEditor) nothing occurs. + control : wxTextCtrl + The wx Object to be acted on. interaction : instance of command.KeySequence The interaction object holding the sequence of key inputs to be simulated being typed @@ -153,8 +128,6 @@ def key_sequence_text_ctrl(control, interaction, delay): Time delay (in ms) in which each key click in the sequence will be performed. """ - if isinstance(control, wx.StaticText): - return if not control.IsEditable(): raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): diff --git a/traitsui/testing/tester/wx/implementation/text_editor.py b/traitsui/testing/tester/wx/implementation/text_editor.py index ad2cd3508..12a010438 100644 --- a/traitsui/testing/tester/wx/implementation/text_editor.py +++ b/traitsui/testing/tester/wx/implementation/text_editor.py @@ -16,7 +16,7 @@ from traitsui.testing.tester.wx import helpers -def readonly_DisplayedText_handler(wrapper, interaction): +def readonly_displayed_text_handler(wrapper, interaction): ''' Handler for ReadonlyEditor to handle query.DisplayedText interactions. Parameters @@ -37,20 +37,33 @@ def readonly_DisplayedText_handler(wrapper, interaction): return wrapper.target.control.GetValue() elif isinstance(wrapper.target.control, wx.StaticText): return wrapper.target.control.GetLabel() + raise TypeError("readonly_displayed_text_handler expected a UIWrapper with" + " a ReadonlyEditor as a target. ReadonlyEditor control" + " should always be either wx.TextCtrl, or wx.StaticText." + " {} was found".format(wrapper.target.control)) 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. + """ handlers = [ (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_text_ctrl( wrapper.target.control, interaction, wrapper.delay))), (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( wrapper.target.control, interaction, wrapper.delay))), - (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_text_ctrl( + (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_object( wrapper.target.control, wrapper.delay))), ] - for target_class in [CustomEditor, ReadonlyEditor, SimpleEditor]: + for target_class in [CustomEditor, SimpleEditor]: for interaction_class, handler in handlers: registry.register_handler( target_class=target_class, @@ -69,5 +82,5 @@ def register(registry): registry.register_handler( target_class=ReadonlyEditor, interaction_class=query.DisplayedText, - handler=readonly_DisplayedText_handler, + handler=readonly_displayed_text_handler, ) diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index af43a56e9..6ed7ed2c0 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -25,6 +25,7 @@ from traitsui.api import TextEditor, View, Item from traitsui.testing.tester.ui_tester import UITester from traitsui.testing.tester import command, query +from traitsui.testing.tester.exceptions import InteractionNotSupported from traitsui.tests._tools import ( GuiTestAssistant, no_gui_test_assistant, @@ -284,9 +285,11 @@ def test_readonly_editor(self): view = get_view(style="readonly", auto_set=True) tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: + with self.assertRaises(InteractionNotSupported): + tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) # Trying to type should do nothing - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) - tester.find_by_name(ui, "name").perform(command.KeyClick("Space")) + with self.assertRaises(InteractionNotSupported): + tester.find_by_name(ui, "name").perform(command.KeyClick("Space")) display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa self.assertEqual(display_name, "A name") From 405a03077077488b28c3a9904b36c24b48dea1a1 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 27 Aug 2020 13:55:46 -0500 Subject: [PATCH 45/60] flake8 --- traitsui/testing/tester/qt4/helpers.py | 6 +- .../tester/qt4/implementation/text_editor.py | 15 ++-- traitsui/testing/tester/wx/helpers.py | 1 + .../tester/wx/implementation/text_editor.py | 18 +++-- traitsui/tests/editors/test_text_editor.py | 74 ++++++++++--------- 5 files changed, 60 insertions(+), 54 deletions(-) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 2bdd44b30..b7452c750 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -9,8 +9,6 @@ # Thanks for using Enthought open source! # -from functools import reduce - from pyface.qt import QtCore, QtGui from pyface.qt.QtTest import QTest @@ -57,11 +55,11 @@ def displayed_text_qobject(widget): Parameters ---------- - widget : Qwidget + widget : Qwidget The Qwidget object with text to be displayed. Should be one of the following QWidgets: 1) QtGui.QLineEdit 2) QtGui.QTextEdit or 3) QtGui.QLabel - + Notes ----- Qt SimpleEditors occassionally use QtGui.QTextEdit as their control, and diff --git a/traitsui/testing/tester/qt4/implementation/text_editor.py b/traitsui/testing/tester/qt4/implementation/text_editor.py index dfdf23554..c69f735e5 100644 --- a/traitsui/testing/tester/qt4/implementation/text_editor.py +++ b/traitsui/testing/tester/qt4/implementation/text_editor.py @@ -26,12 +26,15 @@ def register(registry): """ handlers = [ - (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( - wrapper.target.control, interaction, wrapper.delay))), - (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( - wrapper.target.control, interaction, wrapper.delay))), - (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( - wrapper.target.control, wrapper.delay))), + (command.KeySequence, + (lambda wrapper, interaction: helpers.key_sequence_qwidget( + wrapper.target.control, interaction, wrapper.delay))), + (command.KeyClick, + (lambda wrapper, interaction: helpers.key_click_qwidget( + wrapper.target.control, interaction, wrapper.delay))), + (command.MouseClick, + (lambda wrapper, _: helpers.mouse_click_qwidget( + wrapper.target.control, wrapper.delay))), ] for target_class in [CustomEditor, SimpleEditor]: for interaction_class, handler in handlers: diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 6ba2ce4da..e1af7dbb9 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -71,6 +71,7 @@ def mouse_click_button(control, delay): ) control.ProcessEvent(click_event) + def mouse_click_object(control, delay): """ Performs a mouce click on a wxTextCtrl. diff --git a/traitsui/testing/tester/wx/implementation/text_editor.py b/traitsui/testing/tester/wx/implementation/text_editor.py index 12a010438..827142dfb 100644 --- a/traitsui/testing/tester/wx/implementation/text_editor.py +++ b/traitsui/testing/tester/wx/implementation/text_editor.py @@ -39,7 +39,7 @@ def readonly_displayed_text_handler(wrapper, interaction): return wrapper.target.control.GetLabel() raise TypeError("readonly_displayed_text_handler expected a UIWrapper with" " a ReadonlyEditor as a target. ReadonlyEditor control" - " should always be either wx.TextCtrl, or wx.StaticText." + " should always be either wx.TextCtrl, or wx.StaticText." " {} was found".format(wrapper.target.control)) @@ -55,12 +55,15 @@ def register(registry): """ handlers = [ - (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_text_ctrl( - wrapper.target.control, interaction, wrapper.delay))), - (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( - wrapper.target.control, interaction, wrapper.delay))), - (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_object( - wrapper.target.control, wrapper.delay))), + (command.KeyClick, + (lambda wrapper, interaction: helpers.key_click_text_ctrl( + wrapper.target.control, interaction, wrapper.delay))), + (command.KeySequence, + (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( + wrapper.target.control, interaction, wrapper.delay))), + (command.MouseClick, + (lambda wrapper, _: helpers.mouse_click_object( + wrapper.target.control, wrapper.delay))), ] for target_class in [CustomEditor, SimpleEditor]: @@ -77,7 +80,6 @@ def register(registry): interaction_class=query.DisplayedText, handler=lambda wrapper, _: wrapper.target.control.GetValue(), ) - registry.register_handler( target_class=ReadonlyEditor, diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index 6ed7ed2c0..6f6ab96ff 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -168,9 +168,10 @@ def test_simple_auto_set_update_text(self): tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: with self.assertTraitChanges(foo, "name", count=3): - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) # noqa + name_field = tester.find_by_name(ui, "name") + name_field.perform(command.KeySequence("NEW")) # with auto-set the displayed name should match the name trait - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) @@ -183,15 +184,16 @@ def test_simple_auto_set_false_do_not_update_qt(self): view = get_view(style="simple", auto_set=False) tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + name_field = tester.find_by_name(ui, "name") + name_field.perform(command.KeySequence("NEW")) # with auto-set as False the displayed name should match what has # been typed not the trait itself, After "Enter" is pressed it # should match the name trait - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "") self.assertEqual(display_name, "NEW") - tester.find_by_name(ui, "name").perform(command.KeyClick("Enter")) - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + name_field.perform(command.KeyClick("Enter")) + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) @@ -203,24 +205,23 @@ def test_simple_auto_set_false_do_not_update_wx(self): view = View( Item("name", editor=TextEditor(auto_set=False), - style="simple" - ), + style="simple"), Item("nickname", editor=TextEditor(auto_set=False), - style="simple" - ) - ) + style="simple") + ) tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + name_field = tester.find_by_name(ui, "name") + name_field.perform(command.KeySequence("NEW")) # with auto-set as False the displayed name should match what has - # been typed not the trait itself, After moving to another textbox it - # should match the name trait - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + # been typed not the trait itself, After moving to another textbox + # it should match the name trait + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "") self.assertEqual(display_name, "NEW") tester.find_by_name(ui, "nickname").perform(command.MouseClick()) - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) @@ -231,13 +232,13 @@ def test_custom_auto_set_true_update_text(self): tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: with self.assertTraitChanges(foo, "name", count=3): - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) # noqa + name_field = tester.find_by_name(ui, "name") + name_field.perform(command.KeySequence("NEW")) # with auto-set the displayed name should match the name trait - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) - @requires_toolkit([ToolkitName.qt]) def test_custom_auto_set_false_update_text(self): # the auto_set flag is disregard for custom editor. (not true on WX) @@ -245,9 +246,10 @@ def test_custom_auto_set_false_update_text(self): view = get_view(auto_set=False, style="custom") tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) - tester.find_by_name(ui, "name").perform(command.KeyClick("Enter")) - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + name_field = tester.find_by_name(ui, "name") + name_field.perform(command.KeySequence("NEW")) + name_field.perform(command.KeyClick("Enter")) + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW\n") self.assertEqual(display_name, foo.name) @@ -259,24 +261,23 @@ def test_custom_auto_set_false_do_not_update_wx(self): view = View( Item("name", editor=TextEditor(auto_set=False), - style="custom" - ), + style="custom"), Item("nickname", editor=TextEditor(auto_set=False), - style="custom" - ) - ) + style="custom") + ) tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + name_field = tester.find_by_name(ui, "name") + name_field.perform(command.KeySequence("NEW")) # with auto-set as False the displayed name should match what has - # been typed not the trait itself, After moving to another textbox it - # should match the name trait - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + # been typed not the trait itself, After moving to another textbox + # it should match the name trait + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "") self.assertEqual(display_name, "NEW") tester.find_by_name(ui, "nickname").perform(command.MouseClick()) - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(foo.name, "NEW") self.assertEqual(display_name, foo.name) @@ -285,12 +286,13 @@ def test_readonly_editor(self): view = get_view(style="readonly", auto_set=True) tester = UITester() with tester.create_ui(foo, dict(view=view)) as ui: + name_field = tester.find_by_name(ui, "name") with self.assertRaises(InteractionNotSupported): - tester.find_by_name(ui, "name").perform(command.KeySequence("NEW")) + name_field.perform(command.KeySequence("NEW")) # Trying to type should do nothing with self.assertRaises(InteractionNotSupported): - tester.find_by_name(ui, "name").perform(command.KeyClick("Space")) - display_name = tester.find_by_name(ui, "name").inspect(query.DisplayedText()) # noqa + name_field.perform(command.KeyClick("Space")) + display_name = name_field.inspect(query.DisplayedText()) self.assertEqual(display_name, "A name") def check_format_func_used(self, style): @@ -309,7 +311,7 @@ def check_format_func_used(self, style): tester.find_by_name(ui, "name").inspect(query.DisplayedText()) ) display_nickname = ( - tester.find_by_name(ui, "nickname").inspect(query.DisplayedText()) # noqa + tester.find_by_name(ui, "nickname").inspect(query.DisplayedText()) # noqa E501 ) self.assertEqual(display_name, "WILLIAM") self.assertEqual(display_nickname, "bill") From a96fb0d1c8ec9ecbff940e1073238a914402499b Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Thu, 27 Aug 2020 15:03:50 -0500 Subject: [PATCH 46/60] refactor for located textbox handler logic --- .../testing/tester/qt4/default_registry.py | 4 +- traitsui/testing/tester/qt4/helpers.py | 53 ------------------- .../tester/qt4/implementation/range_editor.py | 27 ++-------- .../tester/qt4/located_object_handlers.py | 35 ++++++++++++ .../testing/tester/wx/default_registry.py | 4 +- traitsui/testing/tester/wx/helpers.py | 44 --------------- .../tester/wx/implementation/range_editor.py | 7 ++- .../tester/wx/located_object_handlers.py | 35 ++++++++++++ 8 files changed, 84 insertions(+), 125 deletions(-) create mode 100644 traitsui/testing/tester/qt4/located_object_handlers.py create mode 100644 traitsui/testing/tester/wx/located_object_handlers.py diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 77d3e4b84..7a3d88b85 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -9,7 +9,7 @@ # Thanks for using Enthought open source! # -from traitsui.testing.tester.qt4.helpers import get_qobject_registry +from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.qt4.implementation import ( button_editor, range_editor, @@ -26,7 +26,7 @@ def get_default_registry(): The default registry containing implementations for TraitsUI editors that is qt specific. """ - registry = get_qobject_registry() + registry = TargetRegistry() # ButtonEditor button_editor.register(registry) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index a331cf0a2..a3b17cbbe 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -128,56 +128,3 @@ def key_click_qwidget(control, interaction, delay): if not control.isEnabled(): raise Disabled("{!r} is disabled.".format(control)) key_click(control, interaction.key, delay=delay) - - -def get_qobject_registry(): - """ Creates a generic registry for handling/solving qt objects. (i.e. - this registry is independent of TraitsUI) - - Returns - ------- - registry : TargetRegistry - Registry containing qt specific generic handlers and solvers. - """ - registry = TargetRegistry() - - widget_classes = [ - QtGui.QLineEdit, - QtGui.QTextEdit, - QtGui.QPushButton, - ] - handlers = [ - (command.KeySequence, (lambda wrapper, interaction: key_sequence_qwidget( - wrapper.target, interaction.sequence, wrapper.delay))), - (command.KeyClick, (lambda wrapper, interaction: key_click_qwidget( - wrapper.target, interaction.key, wrapper.delay))), - (command.MouseClick, (lambda wrapper, _: mouse_click_qwidget( - wrapper.target, wrapper.delay))), - ] - for widget_class in widget_classes: - for interaction_class, handler in handlers: - registry.register_handler( - target_class=widget_class, - interaction_class=interaction_class, - handler=handler, - ) - - registry.register_handler( - target_class=QtGui.QLineEdit, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.displayText(), - ) - - registry.register_handler( - target_class=QtGui.QTextEdit, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.toPlainText(), - ) - - registry.register_handler( - target_class=QtGui.QPushButton, - interaction_class=query.DisplayedText, - handler=lambda wrapper, _: wrapper.target.text(), - ) - - return registry \ No newline at end of file diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index 494060001..dc66e755e 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -17,31 +17,12 @@ SimpleSpinEditor, ) -from traitsui.testing.tester import command, locator, query -from traitsui.testing.tester.qt4 import helpers +from traitsui.testing.tester import locator +from traitsui.testing.tester.qt4.located_object_handlers import LocatedTextbox -class RangeEditorTextbox: - def __init__(self, textbox): - self.textbox = textbox - - @classmethod - def register(cls, registry): - handlers = [ - (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( - wrapper.target.textbox, interaction.sequence, wrapper.delay))), - (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( - wrapper.target.textbox, interaction.key, wrapper.delay))), - (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( - wrapper.target.textbox, wrapper.delay))), - (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.displayText()), - ] - for interaction_class, handler in handlers: - registry.register_handler( - target_class=cls, - interaction_class=interaction_class, - handler=handler, - ) +class RangeEditorTextbox(LocatedTextbox): + pass def resolve_location_simple_slider(wrapper, location): diff --git a/traitsui/testing/tester/qt4/located_object_handlers.py b/traitsui/testing/tester/qt4/located_object_handlers.py new file mode 100644 index 000000000..fee4a9acf --- /dev/null +++ b/traitsui/testing/tester/qt4/located_object_handlers.py @@ -0,0 +1,35 @@ +# 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.testing.tester import command, locator, query +from traitsui.testing.tester.qt4 import helpers + +class LocatedTextbox: + def __init__(self, textbox): + self.textbox = textbox + + @classmethod + def register(cls, registry): + handlers = [ + (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( + wrapper.target.textbox, interaction.sequence, wrapper.delay))), + (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( + wrapper.target.textbox, interaction.key, wrapper.delay))), + (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( + wrapper.target.textbox, wrapper.delay))), + (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.displayText()), + ] + for interaction_class, handler in handlers: + registry.register_handler( + target_class=cls, + interaction_class=interaction_class, + handler=handler, + ) \ No newline at end of file diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 4e0cc6312..34e395ff6 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -9,7 +9,7 @@ # Thanks for using Enthought open source! # -from traitsui.testing.tester.wx.helpers import get_wobject_registry +from traitsui.testing.tester.registry import TargetRegistry from traitsui.testing.tester.wx.implementation import ( button_editor, range_editor, @@ -26,7 +26,7 @@ def get_default_registry(): The default registry containing implementations for TraitsUI editors that is wx specific. """ - registry = get_wobject_registry() + registry = TargetRegistry() # ButtonEditor button_editor.register(registry) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index ba3311246..3c8d24bb7 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -137,47 +137,3 @@ def key_sequence_text_ctrl(control, interaction, delay): control.SetFocus() for char in interaction.sequence: key_click(control, char, delay) - - -def get_wobject_registry(): - """ Creates a generic registry for handling/solving wx objects. (i.e. - this registry is independent of TraitsUI) - Returns - ------- - registry : TargetRegistry - Registry containing wx specific generic handlers and solvers. - """ - registry = TargetRegistry() - - - handlers = [ - (command.KeyClick, (lambda wrapper, interaction: key_click_text_ctrl( - wrapper.target, interaction.key, wrapper.delay))), - (command.KeySequence, (lambda wrapper, interaction: key_sequence_text_ctrl( - wrapper.target, interaction.sequence, wrapper.delay))), - (query.DisplayedText, lambda wrapper, _: wrapper.target.GetValue()), - ] - - for interaction_class, handler in handlers: - registry.register_handler( - target_class=wx.TextCtrl, - interaction_class=interaction_class, - handler=handler - ) - - registry.register_handler( - target_class=wx.StaticText, - interaction_class=query.DisplayedText, - handler=lambda wrapper, action: ( - wrapper.target.GetLabel() - ), - ) - - registry.register_handler( - target_class=wx.Button, - interaction_class=command.MouseClick, - handler=lambda wrapper, _: mouse_click_button(wrapper.target, wrapper.delay) - ) - - return registry - \ No newline at end of file diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index 9e023174e..d7d7046ea 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -17,10 +17,14 @@ ) from traitsui.testing.tester import locator +from traitsui.testing.tester.wx.located_object_handlers import LocatedTextbox + +class RangeEditorTextbox(LocatedTextbox): + pass def resolve_location_simple_slider(wrapper, location): if location == locator.WidgetType.textbox: - return wrapper.editor.control.text + return RangeEditorTextbox(textbox=wrapper.editor.control.text) raise NotImplementedError() @@ -37,3 +41,4 @@ def register(registry): locator_class=locator.WidgetType, solver=resolve_location_simple_slider, ) + RangeEditorTextbox.register(registry) diff --git a/traitsui/testing/tester/wx/located_object_handlers.py b/traitsui/testing/tester/wx/located_object_handlers.py new file mode 100644 index 000000000..815b04eb5 --- /dev/null +++ b/traitsui/testing/tester/wx/located_object_handlers.py @@ -0,0 +1,35 @@ +# 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.testing.tester import command, locator, query +from traitsui.testing.tester.wx import helpers + +class LocatedTextbox: + def __init__(self, textbox): + self.textbox = textbox + + @classmethod + def register(cls, registry): + handlers = [ + (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( + wrapper.target.textbox, interaction.sequence, wrapper.delay))), + (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_text_ctrl( + wrapper.target.textbox, interaction.key, wrapper.delay))), + (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_object( + wrapper.target.textbox, wrapper.delay))), + (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.GetValue()), + ] + for interaction_class, handler in handlers: + registry.register_handler( + target_class=cls, + interaction_class=interaction_class, + handler=handler, + ) From 7577c8176dc79672994bef1ea616ef996812ec49 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 07:47:34 -0500 Subject: [PATCH 47/60] fixing broken tests --- traitsui/qt4/range_editor.py | 3 ++ .../tester/qt4/implementation/range_editor.py | 16 ++++-- .../tester/qt4/located_object_handlers.py | 4 +- .../tester/wx/implementation/range_editor.py | 3 +- .../tester/wx/located_object_handlers.py | 4 +- traitsui/tests/editors/test_range_editor.py | 54 +++++++++++++++++-- 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/traitsui/qt4/range_editor.py b/traitsui/qt4/range_editor.py index 7f378d4f5..de7e186ab 100644 --- a/traitsui/qt4/range_editor.py +++ b/traitsui/qt4/range_editor.py @@ -449,6 +449,9 @@ def update_object_on_scroll(self, pos): def update_object_on_enter(self): """ Handles the user pressing the Enter key in the text field. """ + # it is possible we get the event after the control has gone away + if self.control is None: + return try: self.value = eval(str(self.control.text.text()).strip()) except TraitError as excp: diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index dc66e755e..ce6dee65e 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -27,21 +27,31 @@ class RangeEditorTextbox(LocatedTextbox): def resolve_location_simple_slider(wrapper, location): if location == locator.WidgetType.textbox: - return RangeEditorTextbox(textbox=wrapper.editor.control.text) + return RangeEditorTextbox(textbox=wrapper.target.control.text) raise NotImplementedError() +def resolve_location_range_text(wrapper, location): + if location == locator.WidgetType.textbox: + return RangeEditorTextbox(textbox=wrapper.target.control) + + raise NotImplementedError() def register(registry): targets = [SimpleSliderEditor, LogRangeSliderEditor, - LargeRangeSliderEditor, - RangeTextEditor] + LargeRangeSliderEditor] for target_class in targets: registry.register_solver( target_class=target_class, locator_class=locator.WidgetType, solver=resolve_location_simple_slider, ) + + registry.register_solver( + target_class=RangeTextEditor, + locator_class=locator.WidgetType, + solver=resolve_location_range_text, + ) RangeEditorTextbox.register(registry) diff --git a/traitsui/testing/tester/qt4/located_object_handlers.py b/traitsui/testing/tester/qt4/located_object_handlers.py index fee4a9acf..dc92111b2 100644 --- a/traitsui/testing/tester/qt4/located_object_handlers.py +++ b/traitsui/testing/tester/qt4/located_object_handlers.py @@ -20,9 +20,9 @@ def __init__(self, textbox): def register(cls, registry): handlers = [ (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( - wrapper.target.textbox, interaction.sequence, wrapper.delay))), + wrapper.target.textbox, interaction, wrapper.delay))), (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( - wrapper.target.textbox, interaction.key, wrapper.delay))), + wrapper.target.textbox, interaction, wrapper.delay))), (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( wrapper.target.textbox, wrapper.delay))), (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.displayText()), diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index d7d7046ea..95f659e3e 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -24,7 +24,8 @@ class RangeEditorTextbox(LocatedTextbox): def resolve_location_simple_slider(wrapper, location): if location == locator.WidgetType.textbox: - return RangeEditorTextbox(textbox=wrapper.editor.control.text) + print(wrapper.target) + return RangeEditorTextbox(textbox=wrapper.target.control.text) raise NotImplementedError() diff --git a/traitsui/testing/tester/wx/located_object_handlers.py b/traitsui/testing/tester/wx/located_object_handlers.py index 815b04eb5..90b6b83d0 100644 --- a/traitsui/testing/tester/wx/located_object_handlers.py +++ b/traitsui/testing/tester/wx/located_object_handlers.py @@ -20,9 +20,9 @@ def __init__(self, textbox): def register(cls, registry): handlers = [ (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( - wrapper.target.textbox, interaction.sequence, wrapper.delay))), + wrapper.target.textbox, interaction, wrapper.delay))), (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_text_ctrl( - wrapper.target.textbox, interaction.key, wrapper.delay))), + wrapper.target.textbox, interaction, wrapper.delay))), (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_object( wrapper.target.textbox, wrapper.delay))), (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.GetValue()), diff --git a/traitsui/tests/editors/test_range_editor.py b/traitsui/tests/editors/test_range_editor.py index 1041bc649..7426e6759 100644 --- a/traitsui/tests/editors/test_range_editor.py +++ b/traitsui/tests/editors/test_range_editor.py @@ -1,7 +1,9 @@ import unittest -from traits.api import HasTraits, Int -from traitsui.api import RangeEditor, UItem, View +from traits.api import HasTraits, Int, Range +from traitsui.api import Item, RangeEditor, UItem, View +from traitsui.testing.tester import command, locator, query +from traitsui.testing.tester.ui_tester import UITester from traitsui.tests._tools import ( create_ui, requires_toolkit, @@ -14,6 +16,8 @@ class RangeModel(HasTraits): value = Int() +class stuff(HasTraits): + value = Range(0, 12) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) class TestRangeEditor(unittest.TestCase): @@ -33,8 +37,8 @@ def check_range_enum_editor_format_func(self, style): ) ) - with reraise_exceptions(),\ - create_ui(obj, dict(view=view)) as ui: + tester = UITester() + with tester.create_ui(obj, dict(view=view)) as ui: editor = ui.get_editors("value")[0] # No formatting - simple strings @@ -49,3 +53,45 @@ def test_simple_editor_format_func(self): def test_custom_editor_format_func(self): self.check_range_enum_editor_format_func("custom") + + """def test_set_with_text(self): + model = stuff() + view = View(Item("value", style='simple')) + tester = UITester() + with tester.create_ui(model, dict(view=view)) as ui: + number_field = tester.find_by_name(ui, "value") + text = number_field.locate(locator.WidgetType.textbox) + text.perform(command.KeySequence("\b\b\b\b4")) + text.perform(command.KeyClick("Tab")) + display = text.inspect(query.DisplayedText()) + print("________-") + print(display) + self.assertEqual(model.value, 4) + text.perform(command.KeySequence("\b4"))""" + + @requires_toolkit([ToolkitName.qt]) + def check_set_with_text(self, mode): + model = RangeModel() + view = View(Item("value", editor=RangeEditor(low=0.0, high=12.0, mode=mode)), buttons=["OK"]) + tester = UITester() + with tester.create_ui(model, dict(view=view)) as ui: + number_field = tester.find_by_name(ui, "value") + text = number_field.locate(locator.WidgetType.textbox) + text.perform(command.KeySequence("\b\b\b\b\b4")) + #ok_button = tester.find_by_name(ui, 'OK') + #ok_button.perform(command.MouseClick) + text.perform(command.KeyClick("Enter")) + self.assertEqual(model.value, 4) + #text.perform(command.KeySequence("\b4")) + + def test_simple_slider_editor_set_with_text(self): + return self.check_set_with_text(mode='slider') + + def test_large_range_slider_editor_set_with_text(self): + return self.check_set_with_text(mode='xslider') + + """def test_log_range_slider_editor_set_with_text(self): + return self.check_set_with_text(mode='logslider')""" + + def test_range_text_editor_set_with_text(self): + return self.check_set_with_text(mode='text') \ No newline at end of file From 1a0a7a9ddd885c7b8cca21e8ee803d05b46a777c Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 08:21:30 -0500 Subject: [PATCH 48/60] starting to fix wx tests --- .../tester/wx/implementation/range_editor.py | 15 ++++-- traitsui/tests/editors/test_range_editor.py | 54 +++++++++++-------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index 95f659e3e..fc5f2e389 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -24,22 +24,31 @@ class RangeEditorTextbox(LocatedTextbox): def resolve_location_simple_slider(wrapper, location): if location == locator.WidgetType.textbox: - print(wrapper.target) return RangeEditorTextbox(textbox=wrapper.target.control.text) raise NotImplementedError() +def resolve_location_range_text(wrapper, location): + if location == locator.WidgetType.textbox: + return RangeEditorTextbox(textbox=wrapper.target.control) + + raise NotImplementedError() + def register(registry): targets = [SimpleSliderEditor, LogRangeSliderEditor, - LargeRangeSliderEditor, - RangeTextEditor] + LargeRangeSliderEditor] for target_class in targets: registry.register_solver( target_class=target_class, locator_class=locator.WidgetType, solver=resolve_location_simple_slider, ) + registry.register_solver( + target_class=RangeTextEditor, + locator_class=locator.WidgetType, + solver=resolve_location_simple_slider, + ) RangeEditorTextbox.register(registry) diff --git a/traitsui/tests/editors/test_range_editor.py b/traitsui/tests/editors/test_range_editor.py index 7426e6759..7afa1ba1b 100644 --- a/traitsui/tests/editors/test_range_editor.py +++ b/traitsui/tests/editors/test_range_editor.py @@ -16,8 +16,8 @@ class RangeModel(HasTraits): value = Int() -class stuff(HasTraits): - value = Range(0, 12) +class RangeTrait(HasTraits): + value = Range(1, 12) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) class TestRangeEditor(unittest.TestCase): @@ -54,44 +54,52 @@ def test_simple_editor_format_func(self): def test_custom_editor_format_func(self): self.check_range_enum_editor_format_func("custom") - """def test_set_with_text(self): - model = stuff() - view = View(Item("value", style='simple')) + @requires_toolkit([ToolkitName.qt]) + def check_set_with_text(self, mode): + model = RangeModel() + view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode)), buttons=["OK"]) tester = UITester() with tester.create_ui(model, dict(view=view)) as ui: number_field = tester.find_by_name(ui, "value") text = number_field.locate(locator.WidgetType.textbox) - text.perform(command.KeySequence("\b\b\b\b4")) - text.perform(command.KeyClick("Tab")) - display = text.inspect(query.DisplayedText()) - print("________-") - print(display) + text.perform(command.KeySequence("\b\b\b\b\b4")) + text.perform(command.KeyClick("Enter")) self.assertEqual(model.value, 4) - text.perform(command.KeySequence("\b4"))""" - @requires_toolkit([ToolkitName.qt]) - def check_set_with_text(self, mode): + def test_simple_slider_editor_set_with_text(self): + return self.check_set_with_text(mode='slider') + + def test_large_range_slider_editor_set_with_text(self): + return self.check_set_with_text(mode='xslider') + + def test_log_range_slider_editor_set_with_text(self): + return self.check_set_with_text(mode='logslider') + + def test_range_text_editor_set_with_text(self): + return self.check_set_with_text(mode='text') + + + # "Enter" is not currently working to set the value on wx. + @requires_toolkit([ToolkitName.wx]) + def check_set_with_text_wx(self, mode): model = RangeModel() - view = View(Item("value", editor=RangeEditor(low=0.0, high=12.0, mode=mode)), buttons=["OK"]) + view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode)), buttons=["OK"]) tester = UITester() with tester.create_ui(model, dict(view=view)) as ui: number_field = tester.find_by_name(ui, "value") text = number_field.locate(locator.WidgetType.textbox) text.perform(command.KeySequence("\b\b\b\b\b4")) - #ok_button = tester.find_by_name(ui, 'OK') - #ok_button.perform(command.MouseClick) - text.perform(command.KeyClick("Enter")) + tester.find_by_name(ui, "OK").perform(command.MouseClick) self.assertEqual(model.value, 4) - #text.perform(command.KeySequence("\b4")) - def test_simple_slider_editor_set_with_text(self): - return self.check_set_with_text(mode='slider') + """def test_simple_slider_editor_set_with_text_wx(self): + return self.check_set_with_text_wx(mode='slider') def test_large_range_slider_editor_set_with_text(self): return self.check_set_with_text(mode='xslider') - """def test_log_range_slider_editor_set_with_text(self): - return self.check_set_with_text(mode='logslider')""" + def test_log_range_slider_editor_set_with_text(self): + return self.check_set_with_text(mode='logslider') def test_range_text_editor_set_with_text(self): - return self.check_set_with_text(mode='text') \ No newline at end of file + return self.check_set_with_text(mode='text')""" \ No newline at end of file From a287c52698bd9061571ea13213b7b3ab96e6c050 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 11:02:52 -0500 Subject: [PATCH 49/60] fixing broken wx tests --- .../testing/tester/qt4/default_registry.py | 3 ++ traitsui/testing/tester/qt4/helpers.py | 3 +- .../tester/qt4/implementation/range_editor.py | 9 ++---- .../testing/tester/wx/default_registry.py | 3 ++ traitsui/testing/tester/wx/helpers.py | 9 +++++- .../tester/wx/implementation/range_editor.py | 9 ++---- traitsui/tests/editors/test_range_editor.py | 32 ++++++++++--------- traitsui/tests/editors/test_text_editor.py | 2 +- traitsui/wx/range_editor.py | 4 +++ 9 files changed, 43 insertions(+), 31 deletions(-) diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 7a3d88b85..6321c461b 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -10,6 +10,7 @@ # from traitsui.testing.tester.registry import TargetRegistry +from traitsui.testing.tester.qt4 import located_object_handlers from traitsui.testing.tester.qt4.implementation import ( button_editor, range_editor, @@ -28,6 +29,8 @@ def get_default_registry(): """ registry = TargetRegistry() + located_object_handlers.LocatedTextbox.register(registry) + # ButtonEditor button_editor.register(registry) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index b7452c750..5bc8236ea 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -31,7 +31,7 @@ def key_click(widget, key, delay=0): delay : int Time delay (in ms) in which the key click will be performed. """ - + print(key) mapping = {name: event for event, name in _KEY_MAP.items()} if key not in mapping: raise ValueError( @@ -107,6 +107,7 @@ def key_sequence_qwidget(control, interaction, delay): """ if not control.isEnabled(): raise Disabled("{!r} is disabled.".format(control)) + print(repr(interaction.sequence)) QTest.keyClicks(control, interaction.sequence, delay=delay) diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index ce6dee65e..e7812f2f2 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -21,19 +21,15 @@ from traitsui.testing.tester.qt4.located_object_handlers import LocatedTextbox -class RangeEditorTextbox(LocatedTextbox): - pass - - def resolve_location_simple_slider(wrapper, location): if location == locator.WidgetType.textbox: - return RangeEditorTextbox(textbox=wrapper.target.control.text) + return LocatedTextbox(textbox=wrapper.target.control.text) raise NotImplementedError() def resolve_location_range_text(wrapper, location): if location == locator.WidgetType.textbox: - return RangeEditorTextbox(textbox=wrapper.target.control) + return LocatedTextbox(textbox=wrapper.target.control) raise NotImplementedError() @@ -54,4 +50,3 @@ def register(registry): locator_class=locator.WidgetType, solver=resolve_location_range_text, ) - RangeEditorTextbox.register(registry) diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index 34e395ff6..bed4e76d5 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -10,6 +10,7 @@ # from traitsui.testing.tester.registry import TargetRegistry +from traitsui.testing.tester.wx import located_object_handlers from traitsui.testing.tester.wx.implementation import ( button_editor, range_editor, @@ -28,6 +29,8 @@ def get_default_registry(): """ registry = TargetRegistry() + located_object_handlers.LocatedTextbox.register(registry) + # ButtonEditor button_editor.register(registry) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 73a1ea71c..e97acb3ff 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -110,7 +110,14 @@ def key_click_text_ctrl(control, interaction, delay): raise Disabled("{!r} is disabled.".format(control)) if not control.HasFocus(): control.SetFocus() - key_click(control, interaction.key, delay) + # EmulateKeyPress in key_click seems to not be handling "Enter" + # correctly. This is a temporary workaround. + if interaction.key == "Enter": + wx.MilliSleep(delay) + event = wx.CommandEvent(wx.EVT_TEXT_ENTER.typeId, control.GetId()) + control.ProcessEvent(event) + else: + key_click(control, interaction.key, delay) def key_sequence_text_ctrl(control, interaction, delay): diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index fc5f2e389..e4fdf5ae1 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -19,18 +19,16 @@ from traitsui.testing.tester import locator from traitsui.testing.tester.wx.located_object_handlers import LocatedTextbox -class RangeEditorTextbox(LocatedTextbox): - pass def resolve_location_simple_slider(wrapper, location): if location == locator.WidgetType.textbox: - return RangeEditorTextbox(textbox=wrapper.target.control.text) + return LocatedTextbox(textbox=wrapper.target.control.text) raise NotImplementedError() def resolve_location_range_text(wrapper, location): if location == locator.WidgetType.textbox: - return RangeEditorTextbox(textbox=wrapper.target.control) + return LocatedTextbox(textbox=wrapper.target.control) raise NotImplementedError() @@ -49,6 +47,5 @@ def register(registry): registry.register_solver( target_class=RangeTextEditor, locator_class=locator.WidgetType, - solver=resolve_location_simple_slider, + solver=resolve_location_range_text, ) - RangeEditorTextbox.register(registry) diff --git a/traitsui/tests/editors/test_range_editor.py b/traitsui/tests/editors/test_range_editor.py index 7afa1ba1b..901df6efa 100644 --- a/traitsui/tests/editors/test_range_editor.py +++ b/traitsui/tests/editors/test_range_editor.py @@ -16,8 +16,6 @@ class RangeModel(HasTraits): value = Int() -class RangeTrait(HasTraits): - value = Range(1, 12) @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) class TestRangeEditor(unittest.TestCase): @@ -57,7 +55,7 @@ def test_custom_editor_format_func(self): @requires_toolkit([ToolkitName.qt]) def check_set_with_text(self, mode): model = RangeModel() - view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode)), buttons=["OK"]) + view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode, enter_set=True))) tester = UITester() with tester.create_ui(model, dict(view=view)) as ui: number_field = tester.find_by_name(ui, "value") @@ -79,27 +77,31 @@ def test_range_text_editor_set_with_text(self): return self.check_set_with_text(mode='text') - # "Enter" is not currently working to set the value on wx. + # There is a problem with KeySequence on wx. trying to include the key + # '\b' does not succesffuly type a backspace. Instead EmulateKeyPress + # seems to literally type "\x08" which lead to errors. @requires_toolkit([ToolkitName.wx]) def check_set_with_text_wx(self, mode): model = RangeModel() - view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode)), buttons=["OK"]) + view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode))) tester = UITester() with tester.create_ui(model, dict(view=view)) as ui: number_field = tester.find_by_name(ui, "value") text = number_field.locate(locator.WidgetType.textbox) - text.perform(command.KeySequence("\b\b\b\b\b4")) - tester.find_by_name(ui, "OK").perform(command.MouseClick) - self.assertEqual(model.value, 4) + for _ in range(5): + text.perform(command.KeyClick("Backspace")) + text.perform(command.KeySequence("10")) + text.perform(command.KeyClick("Enter")) + self.assertEqual(model.value, 10) - """def test_simple_slider_editor_set_with_text_wx(self): + def test_simple_slider_editor_set_with_text_wx(self): return self.check_set_with_text_wx(mode='slider') - def test_large_range_slider_editor_set_with_text(self): - return self.check_set_with_text(mode='xslider') + def test_large_range_slider_editor_set_with_text_wx(self): + return self.check_set_with_text_wx(mode='xslider') - def test_log_range_slider_editor_set_with_text(self): - return self.check_set_with_text(mode='logslider') + def test_log_range_slider_editor_set_with_text_wx(self): + return self.check_set_with_text_wx(mode='logslider') - def test_range_text_editor_set_with_text(self): - return self.check_set_with_text(mode='text')""" \ No newline at end of file + def test_range_text_editor_set_with_text_wx(self): + return self.check_set_with_text_wx(mode='text') \ No newline at end of file diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index 6f6ab96ff..4dc13386e 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -204,7 +204,7 @@ def test_simple_auto_set_false_do_not_update_wx(self): foo = Foo(name="") view = View( Item("name", - editor=TextEditor(auto_set=False), + editor=TextEditor(auto_set=False, enter_set=True), style="simple"), Item("nickname", editor=TextEditor(auto_set=False), diff --git a/traitsui/wx/range_editor.py b/traitsui/wx/range_editor.py index 27b25d566..f5195b468 100644 --- a/traitsui/wx/range_editor.py +++ b/traitsui/wx/range_editor.py @@ -527,6 +527,10 @@ def update_object_on_enter(self, event): """ if isinstance(event, wx.FocusEvent): event.Skip() + # There are cases where this method is called with self.control == + # None. + if self.control is None: + return try: value = self.control.text.GetValue().strip() try: From 7c595ca9a361c6628f703f83e168974e38d98f07 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 11:27:33 -0500 Subject: [PATCH 50/60] flake8 --- traitsui/testing/tester/qt4/helpers.py | 2 -- .../tester/qt4/implementation/range_editor.py | 5 ++-- .../tester/qt4/located_object_handlers.py | 23 +++++++++------- .../tester/wx/implementation/range_editor.py | 2 +- .../tester/wx/located_object_handlers.py | 21 +++++++++------ traitsui/tests/editors/test_range_editor.py | 27 +++++++++++++------ 6 files changed, 50 insertions(+), 30 deletions(-) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 5bc8236ea..ea3c97226 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -31,7 +31,6 @@ def key_click(widget, key, delay=0): delay : int Time delay (in ms) in which the key click will be performed. """ - print(key) mapping = {name: event for event, name in _KEY_MAP.items()} if key not in mapping: raise ValueError( @@ -107,7 +106,6 @@ def key_sequence_qwidget(control, interaction, delay): """ if not control.isEnabled(): raise Disabled("{!r} is disabled.".format(control)) - print(repr(interaction.sequence)) QTest.keyClicks(control, interaction.sequence, delay=delay) diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index e7812f2f2..32fb7daab 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -14,7 +14,6 @@ LogRangeSliderEditor, RangeTextEditor, SimpleSliderEditor, - SimpleSpinEditor, ) from traitsui.testing.tester import locator @@ -27,12 +26,14 @@ def resolve_location_simple_slider(wrapper, location): raise NotImplementedError() + def resolve_location_range_text(wrapper, location): if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) raise NotImplementedError() + def register(registry): targets = [SimpleSliderEditor, @@ -44,7 +45,7 @@ def register(registry): locator_class=locator.WidgetType, solver=resolve_location_simple_slider, ) - + registry.register_solver( target_class=RangeTextEditor, locator_class=locator.WidgetType, diff --git a/traitsui/testing/tester/qt4/located_object_handlers.py b/traitsui/testing/tester/qt4/located_object_handlers.py index dc92111b2..ca3bd9357 100644 --- a/traitsui/testing/tester/qt4/located_object_handlers.py +++ b/traitsui/testing/tester/qt4/located_object_handlers.py @@ -9,9 +9,10 @@ # Thanks for using Enthought open source! # -from traitsui.testing.tester import command, locator, query +from traitsui.testing.tester import command, query from traitsui.testing.tester.qt4 import helpers + class LocatedTextbox: def __init__(self, textbox): self.textbox = textbox @@ -19,17 +20,21 @@ def __init__(self, textbox): @classmethod def register(cls, registry): handlers = [ - (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( - wrapper.target.textbox, interaction, wrapper.delay))), - (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_qwidget( - wrapper.target.textbox, interaction, wrapper.delay))), - (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_qwidget( - wrapper.target.textbox, wrapper.delay))), - (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.displayText()), + (command.KeySequence, + (lambda wrapper, interaction: helpers.key_sequence_qwidget( + wrapper.target.textbox, interaction, wrapper.delay))), + (command.KeyClick, + (lambda wrapper, interaction: helpers.key_click_qwidget( + wrapper.target.textbox, interaction, wrapper.delay))), + (command.MouseClick, + (lambda wrapper, _: helpers.mouse_click_qwidget( + wrapper.target.textbox, wrapper.delay))), + (query.DisplayedText, + lambda wrapper, _: wrapper.target.textbox.displayText()), ] for interaction_class, handler in handlers: registry.register_handler( target_class=cls, interaction_class=interaction_class, handler=handler, - ) \ No newline at end of file + ) diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index e4fdf5ae1..dc2bfa6c6 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -13,7 +13,6 @@ LogRangeSliderEditor, RangeTextEditor, SimpleSliderEditor, - SimpleSpinEditor, ) from traitsui.testing.tester import locator @@ -26,6 +25,7 @@ def resolve_location_simple_slider(wrapper, location): raise NotImplementedError() + def resolve_location_range_text(wrapper, location): if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) diff --git a/traitsui/testing/tester/wx/located_object_handlers.py b/traitsui/testing/tester/wx/located_object_handlers.py index 90b6b83d0..f89ac8621 100644 --- a/traitsui/testing/tester/wx/located_object_handlers.py +++ b/traitsui/testing/tester/wx/located_object_handlers.py @@ -9,9 +9,10 @@ # Thanks for using Enthought open source! # -from traitsui.testing.tester import command, locator, query +from traitsui.testing.tester import command, query from traitsui.testing.tester.wx import helpers + class LocatedTextbox: def __init__(self, textbox): self.textbox = textbox @@ -19,13 +20,17 @@ def __init__(self, textbox): @classmethod def register(cls, registry): handlers = [ - (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( - wrapper.target.textbox, interaction, wrapper.delay))), - (command.KeyClick, (lambda wrapper, interaction: helpers.key_click_text_ctrl( - wrapper.target.textbox, interaction, wrapper.delay))), - (command.MouseClick, (lambda wrapper, _: helpers.mouse_click_object( - wrapper.target.textbox, wrapper.delay))), - (query.DisplayedText, lambda wrapper, _: wrapper.target.textbox.GetValue()), + (command.KeySequence, + (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( + wrapper.target.textbox, interaction, wrapper.delay))), + (command.KeyClick, + (lambda wrapper, interaction: helpers.key_click_text_ctrl( + wrapper.target.textbox, interaction, wrapper.delay))), + (command.MouseClick, + (lambda wrapper, _: helpers.mouse_click_object( + wrapper.target.textbox, wrapper.delay))), + (query.DisplayedText, + lambda wrapper, _: wrapper.target.textbox.GetValue()), ] for interaction_class, handler in handlers: registry.register_handler( diff --git a/traitsui/tests/editors/test_range_editor.py b/traitsui/tests/editors/test_range_editor.py index 901df6efa..dcbc9629c 100644 --- a/traitsui/tests/editors/test_range_editor.py +++ b/traitsui/tests/editors/test_range_editor.py @@ -1,13 +1,11 @@ import unittest -from traits.api import HasTraits, Int, Range +from traits.api import HasTraits, Int from traitsui.api import Item, RangeEditor, UItem, View from traitsui.testing.tester import command, locator, query from traitsui.testing.tester.ui_tester import UITester from traitsui.tests._tools import ( - create_ui, requires_toolkit, - reraise_exceptions, ToolkitName, ) @@ -55,14 +53,21 @@ def test_custom_editor_format_func(self): @requires_toolkit([ToolkitName.qt]) def check_set_with_text(self, mode): model = RangeModel() - view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode, enter_set=True))) + view = View( + Item( + "value", + editor=RangeEditor(low=1, high=12, mode=mode) + ) + ) tester = UITester() with tester.create_ui(model, dict(view=view)) as ui: number_field = tester.find_by_name(ui, "value") text = number_field.locate(locator.WidgetType.textbox) text.perform(command.KeySequence("\b\b\b\b\b4")) text.perform(command.KeyClick("Enter")) + displayed = text.inspect(query.DisplayedText()) self.assertEqual(model.value, 4) + self.assertEqual(displayed, str(model.value)) def test_simple_slider_editor_set_with_text(self): return self.check_set_with_text(mode='slider') @@ -76,14 +81,18 @@ def test_log_range_slider_editor_set_with_text(self): def test_range_text_editor_set_with_text(self): return self.check_set_with_text(mode='text') - # There is a problem with KeySequence on wx. trying to include the key # '\b' does not succesffuly type a backspace. Instead EmulateKeyPress - # seems to literally type "\x08" which lead to errors. + # seems to literally type "\x08" which lead to errors. @requires_toolkit([ToolkitName.wx]) def check_set_with_text_wx(self, mode): model = RangeModel() - view = View(Item("value", editor=RangeEditor(low=1, high=12, mode=mode))) + view = View( + Item( + "value", + editor=RangeEditor(low=1, high=12, mode=mode) + ) + ) tester = UITester() with tester.create_ui(model, dict(view=view)) as ui: number_field = tester.find_by_name(ui, "value") @@ -92,7 +101,9 @@ def check_set_with_text_wx(self, mode): text.perform(command.KeyClick("Backspace")) text.perform(command.KeySequence("10")) text.perform(command.KeyClick("Enter")) + displayed = text.inspect(query.DisplayedText()) self.assertEqual(model.value, 10) + self.assertEqual(displayed, str(model.value)) def test_simple_slider_editor_set_with_text_wx(self): return self.check_set_with_text_wx(mode='slider') @@ -104,4 +115,4 @@ def test_log_range_slider_editor_set_with_text_wx(self): return self.check_set_with_text_wx(mode='logslider') def test_range_text_editor_set_with_text_wx(self): - return self.check_set_with_text_wx(mode='text') \ No newline at end of file + return self.check_set_with_text_wx(mode='text') From 78479332ca126cf7eddcd280bcf09f716f0af893 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 11:31:05 -0500 Subject: [PATCH 51/60] removing commented out old code --- traitsui/testing/tester/qt4/helpers.py | 1 + traitsui/tests/editors/test_range_editor_text.py | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index ea3c97226..8e4933937 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -31,6 +31,7 @@ def key_click(widget, key, delay=0): delay : int Time delay (in ms) in which the key click will be performed. """ + mapping = {name: event for event, name in _KEY_MAP.items()} if key not in mapping: raise ValueError( diff --git a/traitsui/tests/editors/test_range_editor_text.py b/traitsui/tests/editors/test_range_editor_text.py index 546d8c672..87ad01845 100644 --- a/traitsui/tests/editors/test_range_editor_text.py +++ b/traitsui/tests/editors/test_range_editor_text.py @@ -81,17 +81,6 @@ def test_wx_text_editing(self): # the number traits should be between 3 and 8 self.assertTrue(3 <= num.number <= 8) - """ with reraise_exceptions(), create_ui(num) as ui: - - # the following is equivalent to setting the text in the text - # control, then pressing OK - - textctrl = ui.control.FindWindowByName("text") - textctrl.SetValue("1") - - # the number traits should be between 3 and 8 - self.assertTrue(3 <= num.number <= 8) """ - @requires_toolkit([ToolkitName.qt]) def test_avoid_slider_feedback(self): # behavior: when editing the text box part of a range editor, the value From 991be83e1012f81b8787ebc320117f935bd90244 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 11:35:08 -0500 Subject: [PATCH 52/60] missed a flake8 --- traitsui/testing/tester/qt4/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traitsui/testing/tester/qt4/helpers.py b/traitsui/testing/tester/qt4/helpers.py index 8e4933937..b7452c750 100644 --- a/traitsui/testing/tester/qt4/helpers.py +++ b/traitsui/testing/tester/qt4/helpers.py @@ -31,7 +31,7 @@ def key_click(widget, key, delay=0): delay : int Time delay (in ms) in which the key click will be performed. """ - + mapping = {name: event for event, name in _KEY_MAP.items()} if key not in mapping: raise ValueError( From f08e54f9ab38beccb3f4d699b2109a32cc051b4f Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 11:41:36 -0500 Subject: [PATCH 53/60] deleted an old unneeded test --- .../tests/editors/test_range_editor_text.py | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/traitsui/tests/editors/test_range_editor_text.py b/traitsui/tests/editors/test_range_editor_text.py index 87ad01845..46b2f6971 100644 --- a/traitsui/tests/editors/test_range_editor_text.py +++ b/traitsui/tests/editors/test_range_editor_text.py @@ -81,29 +81,9 @@ def test_wx_text_editing(self): # the number traits should be between 3 and 8 self.assertTrue(3 <= num.number <= 8) - @requires_toolkit([ToolkitName.qt]) - def test_avoid_slider_feedback(self): - # behavior: when editing the text box part of a range editor, the value - # should not be adjusted by the slider part of the range editor - from pyface import qt - - num = FloatWithRangeEditor() - tester = UITester() - with tester.create_ui(num) as ui: - - # the following is equivalent to setting the text in the text - # control, then pressing OK - lineedit = ui.control.findChild(qt.QtGui.QLineEdit) - lineedit.setFocus() - lineedit.setText("4") - lineedit.editingFinished.emit() - - # the number trait should be 4 extactly - self.assertEqual(num.number, 4.0) - if __name__ == "__main__": # Executing the file opens the dialog for manual testing - num = NumberWithTextEditor() + num = NumberWithRangeEditor() num.configure_traits() print(num.number) From ee863e5ffc833c3254c42fc9cf7ec5fc123ed3c2 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Fri, 28 Aug 2020 12:34:55 -0500 Subject: [PATCH 54/60] adding docstrings and a function rename --- .../tester/qt4/implementation/range_editor.py | 38 ++++++++++++++++++- .../tester/wx/implementation/range_editor.py | 38 ++++++++++++++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index 32fb7daab..2256f9990 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -20,7 +20,19 @@ from traitsui.testing.tester.qt4.located_object_handlers import LocatedTextbox -def resolve_location_simple_slider(wrapper, location): +def resolve_location_slider(wrapper, location): + """ Solver from a UIWrapper wrapped Range Editor to a LocatedTextbox + containing the textbox of interest + + If there are any conflicts, an error will occur. + + Parameters + ---------- + wrapper : UIWrapper + Wrapper containing the Range Editor target. + location : locator.WidgetType.textbox + The location we are looking to resolve. + """ if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control.text) @@ -28,6 +40,19 @@ def resolve_location_simple_slider(wrapper, location): def resolve_location_range_text(wrapper, location): + """ Solver from a UIWrapper wrapped RangeTextEditor to a LocatedTextbox + containing the textbox of interest + + If there are any conflicts, an error will occur. + + Parameters + ---------- + wrapper : UIWrapper + Wrapper containing the RangeTextEditor target. + location : locator.WidgetType.textbox + The location we are looking to resolve. + """ + if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) @@ -35,6 +60,15 @@ def resolve_location_range_text(wrapper, location): 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. + """ targets = [SimpleSliderEditor, LogRangeSliderEditor, @@ -43,7 +77,7 @@ def register(registry): registry.register_solver( target_class=target_class, locator_class=locator.WidgetType, - solver=resolve_location_simple_slider, + solver=resolve_location_slider, ) registry.register_solver( diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index dc2bfa6c6..db00f9c23 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -19,7 +19,19 @@ from traitsui.testing.tester.wx.located_object_handlers import LocatedTextbox -def resolve_location_simple_slider(wrapper, location): +def resolve_location_slider(wrapper, location): + """ Solver from a UIWrapper wrapped Range Editor to a LocatedTextbox + containing the textbox of interest + + If there are any conflicts, an error will occur. + + Parameters + ---------- + wrapper : UIWrapper + Wrapper containing the Range Editor target. + location : locator.WidgetType.textbox + The location we are looking to resolve. + """ if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control.text) @@ -27,6 +39,19 @@ def resolve_location_simple_slider(wrapper, location): def resolve_location_range_text(wrapper, location): + """ Solver from a UIWrapper wrapped RangeTextEditor to a LocatedTextbox + containing the textbox of interest + + If there are any conflicts, an error will occur. + + Parameters + ---------- + wrapper : UIWrapper + Wrapper containing the RangeTextEditor target. + location : locator.WidgetType.textbox + The location we are looking to resolve. + """ + if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) @@ -34,6 +59,15 @@ def resolve_location_range_text(wrapper, location): 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. + """ targets = [SimpleSliderEditor, LogRangeSliderEditor, @@ -42,7 +76,7 @@ def register(registry): registry.register_solver( target_class=target_class, locator_class=locator.WidgetType, - solver=resolve_location_simple_slider, + solver=resolve_location_slider, ) registry.register_solver( target_class=RangeTextEditor, From 73a97d43df591041fd0666ae6b50dba191973bd1 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 31 Aug 2020 09:02:08 -0500 Subject: [PATCH 55/60] fixing typos and adding a few docstrings --- .../tester/qt4/located_object_handlers.py | 17 +++++++++++++++++ .../tester/wx/located_object_handlers.py | 17 +++++++++++++++++ traitsui/tests/editors/test_range_editor.py | 4 ++-- traitsui/tests/editors/test_text_editor.py | 2 +- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/traitsui/testing/tester/qt4/located_object_handlers.py b/traitsui/testing/tester/qt4/located_object_handlers.py index ca3bd9357..69c293683 100644 --- a/traitsui/testing/tester/qt4/located_object_handlers.py +++ b/traitsui/testing/tester/qt4/located_object_handlers.py @@ -14,11 +14,28 @@ class LocatedTextbox: + """ Wrapper class for a located Textbox in Qt. + """ def __init__(self, textbox): + """ + Parameters + ---------- + textbox : Instance of QtGui.QLineEdit + """ self.textbox = textbox @classmethod def register(cls, registry): + """ Class method to register interactions on a LocatedTextbox for the + given registry. + + If there are any conflicts, an error will occur. + + Parameters + ---------- + registry : TargetRegistry + The registry being registered to. + """ handlers = [ (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_qwidget( diff --git a/traitsui/testing/tester/wx/located_object_handlers.py b/traitsui/testing/tester/wx/located_object_handlers.py index f89ac8621..f33125278 100644 --- a/traitsui/testing/tester/wx/located_object_handlers.py +++ b/traitsui/testing/tester/wx/located_object_handlers.py @@ -14,11 +14,28 @@ class LocatedTextbox: + """ Wrapper class for a located Textbox in Wx. + """ def __init__(self, textbox): + """ + Parameters + ---------- + textbox : Instance of wx.TextCtrl + """ self.textbox = textbox @classmethod def register(cls, registry): + """ Class method to register interactions on a LocatedTextbox for the + given registry. + + If there are any conflicts, an error will occur. + + Parameters + ---------- + registry : TargetRegistry + The registry being registered to. + """ handlers = [ (command.KeySequence, (lambda wrapper, interaction: helpers.key_sequence_text_ctrl( diff --git a/traitsui/tests/editors/test_range_editor.py b/traitsui/tests/editors/test_range_editor.py index dcbc9629c..ddccbdfa7 100644 --- a/traitsui/tests/editors/test_range_editor.py +++ b/traitsui/tests/editors/test_range_editor.py @@ -82,8 +82,8 @@ def test_range_text_editor_set_with_text(self): return self.check_set_with_text(mode='text') # There is a problem with KeySequence on wx. trying to include the key - # '\b' does not succesffuly type a backspace. Instead EmulateKeyPress - # seems to literally type "\x08" which lead to errors. + # '\b' does not succesfully type a backspace. Instead EmulateKeyPress + # seems to literally type "\x08" which leads to errors. @requires_toolkit([ToolkitName.wx]) def check_set_with_text_wx(self, mode): model = RangeModel() diff --git a/traitsui/tests/editors/test_text_editor.py b/traitsui/tests/editors/test_text_editor.py index 4dc13386e..6f6ab96ff 100644 --- a/traitsui/tests/editors/test_text_editor.py +++ b/traitsui/tests/editors/test_text_editor.py @@ -204,7 +204,7 @@ def test_simple_auto_set_false_do_not_update_wx(self): foo = Foo(name="") view = View( Item("name", - editor=TextEditor(auto_set=False, enter_set=True), + editor=TextEditor(auto_set=False), style="simple"), Item("nickname", editor=TextEditor(auto_set=False), From b806e3a93dd47ac339d3b80882dc12fd4b19f576 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 1 Sep 2020 09:04:06 -0500 Subject: [PATCH 56/60] making suggested changes --- traitsui/qt4/range_editor.py | 6 ++-- traitsui/testing/tester/locator.py | 7 +++- .../tester/qt4/implementation/range_editor.py | 26 ++++++++++---- .../tester/qt4/located_object_handlers.py | 18 +++++++--- traitsui/testing/tester/wx/helpers.py | 2 +- .../tester/wx/implementation/range_editor.py | 34 +++++++++++++------ .../tester/wx/located_object_handlers.py | 18 +++++++--- traitsui/wx/range_editor.py | 8 ++--- 8 files changed, 85 insertions(+), 34 deletions(-) diff --git a/traitsui/qt4/range_editor.py b/traitsui/qt4/range_editor.py index de7e186ab..e5e632054 100644 --- a/traitsui/qt4/range_editor.py +++ b/traitsui/qt4/range_editor.py @@ -177,7 +177,8 @@ def update_object_on_scroll(self, pos): def update_object_on_enter(self): """ Handles the user pressing the Enter key in the text field. """ - # it is possible we get the event after the control has gone away + # It is possible the event is processed after the control is removed + # from the editor if self.control is None: return @@ -449,7 +450,8 @@ def update_object_on_scroll(self, pos): def update_object_on_enter(self): """ Handles the user pressing the Enter key in the text field. """ - # it is possible we get the event after the control has gone away + # It is possible the event is processed after the control is removed + # from the editor if self.control is None: return try: diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index 51aa83e6c..3bb001dd6 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -40,7 +40,12 @@ def __init__(self, name): class WidgetType(enum.Enum): - """ An Enum of widget types. + """ A locator for locating nested widgets within a UI. Many editors will + contain many sub-widgets (e.g. a textbox, slider, tabs, buttons, etc.). + + For example when working with a range editor, one could call + tester.find_by_name(ui, "ranged_number").locate(locator.WidgetType.textbox) """ + # A textbox within a UI textbox = "textbox" diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index 2256f9990..31a64593a 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -30,13 +30,20 @@ def resolve_location_slider(wrapper, location): ---------- wrapper : UIWrapper Wrapper containing the Range Editor target. - location : locator.WidgetType.textbox + location : locator.WidgetType The location we are looking to resolve. """ if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control.text) - - raise NotImplementedError() + if location in [locator.WidgetType.slider]: + raise NotImplementedError( + f"Logic for interacting with the {location}" + " has not been implemented." + ) + raise ValueError( + f"Unable to resolve {location} on {wrapper.target}." + " Currently supported: {locator.WidgetType.textbox}" + ) def resolve_location_range_text(wrapper, location): @@ -49,14 +56,21 @@ def resolve_location_range_text(wrapper, location): ---------- wrapper : UIWrapper Wrapper containing the RangeTextEditor target. - location : locator.WidgetType.textbox + location : locator.WidgetType The location we are looking to resolve. """ if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) - - raise NotImplementedError() + if location in [locator.WidgetType.slider]: + raise NotImplementedError( + f"Logic for interacting with the {location}" + " has not been implemented." + ) + raise ValueError( + f"Unable to resolve {location} on {wrapper.target}." + " Currently supported: {locator.WidgetType.textbox}" + ) def register(registry): diff --git a/traitsui/testing/tester/qt4/located_object_handlers.py b/traitsui/testing/tester/qt4/located_object_handlers.py index 69c293683..211b4b658 100644 --- a/traitsui/testing/tester/qt4/located_object_handlers.py +++ b/traitsui/testing/tester/qt4/located_object_handlers.py @@ -9,19 +9,27 @@ # Thanks for using Enthought open source! # +""" This module contains targets for UIWrapper so that the logic related to +them can be reused. All handlers and solvers for these objects are +registered to the default registry via the register class methods. To use the +logic in these objects, one simply needs to register a solver with their +target_class of choice to one of these as the locator_class. For an example, +see the implementation of range_editor. +""" + from traitsui.testing.tester import command, query from traitsui.testing.tester.qt4 import helpers class LocatedTextbox: """ Wrapper class for a located Textbox in Qt. + + Parameters + ---------- + textbox : Instance of QtGui.QLineEdit """ + def __init__(self, textbox): - """ - Parameters - ---------- - textbox : Instance of QtGui.QLineEdit - """ self.textbox = textbox @classmethod diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index e97acb3ff..985329d1b 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -111,7 +111,7 @@ def key_click_text_ctrl(control, interaction, delay): if not control.HasFocus(): control.SetFocus() # EmulateKeyPress in key_click seems to not be handling "Enter" - # correctly. This is a temporary workaround. + # correctly. if interaction.key == "Enter": wx.MilliSleep(delay) event = wx.CommandEvent(wx.EVT_TEXT_ENTER.typeId, control.GetId()) diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index db00f9c23..efa850e44 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -29,13 +29,20 @@ def resolve_location_slider(wrapper, location): ---------- wrapper : UIWrapper Wrapper containing the Range Editor target. - location : locator.WidgetType.textbox + location : locator.WidgetType The location we are looking to resolve. """ if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control.text) - - raise NotImplementedError() + if location in [locator.WidgetType.slider]: + raise NotImplementedError( + f"Logic for interacting with the {location}" + " has not been implemented." + ) + raise ValueError( + f"Unable to resolve {location} on {wrapper.target}." + " Currently supported: {locator.WidgetType.textbox}" + ) def resolve_location_range_text(wrapper, location): @@ -48,14 +55,21 @@ def resolve_location_range_text(wrapper, location): ---------- wrapper : UIWrapper Wrapper containing the RangeTextEditor target. - location : locator.WidgetType.textbox + location : locator.WidgetType The location we are looking to resolve. """ if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) - - raise NotImplementedError() + if location in [locator.WidgetType.slider]: + raise NotImplementedError( + f"Logic for interacting with the {location}" + " has not been implemented." + ) + raise ValueError( + f"Unable to resolve {location} on {wrapper.target}." + " Currently supported: {locator.WidgetType.textbox}" + ) def register(registry): @@ -79,7 +93,7 @@ def register(registry): solver=resolve_location_slider, ) registry.register_solver( - target_class=RangeTextEditor, - locator_class=locator.WidgetType, - solver=resolve_location_range_text, - ) + target_class=RangeTextEditor, + locator_class=locator.WidgetType, + solver=resolve_location_range_text, + ) diff --git a/traitsui/testing/tester/wx/located_object_handlers.py b/traitsui/testing/tester/wx/located_object_handlers.py index f33125278..97f6ed5da 100644 --- a/traitsui/testing/tester/wx/located_object_handlers.py +++ b/traitsui/testing/tester/wx/located_object_handlers.py @@ -9,19 +9,27 @@ # Thanks for using Enthought open source! # +""" This module contains targets for UIWrapper so that the logic related to +them can be reused. All handlers and solvers for these objects are +registered to the default registry via the register class methods. To use the +logic in these objects, one simply needs to register a solver with their +target_class of choice to one of these as the locator_class. For an example, +see the implementation of range_editor. +""" + from traitsui.testing.tester import command, query from traitsui.testing.tester.wx import helpers class LocatedTextbox: """ Wrapper class for a located Textbox in Wx. + + Parameters + ---------- + textbox : Instance of wx.TextCtrl """ + def __init__(self, textbox): - """ - Parameters - ---------- - textbox : Instance of wx.TextCtrl - """ self.textbox = textbox @classmethod diff --git a/traitsui/wx/range_editor.py b/traitsui/wx/range_editor.py index f5195b468..36805ca82 100644 --- a/traitsui/wx/range_editor.py +++ b/traitsui/wx/range_editor.py @@ -527,8 +527,8 @@ def update_object_on_enter(self, event): """ if isinstance(event, wx.FocusEvent): event.Skip() - # There are cases where this method is called with self.control == - # None. + # It is possible the event is processed after the control is removed + # from the editor if self.control is None: return try: @@ -852,8 +852,8 @@ def update_object(self, event): if isinstance(event, wx.FocusEvent): event.Skip() - # There are cases where this method is called with self.control == - # None. + # It is possible the event is processed after the control is removed + # from the editor if self.control is None: return From 78a7bf0c31a2aefd58df01ae35d4baf0471f8d49 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 1 Sep 2020 09:12:21 -0500 Subject: [PATCH 57/60] handling '\b' characters on wx --- traitsui/testing/tester/wx/helpers.py | 2 ++ traitsui/tests/editors/test_range_editor.py | 37 --------------------- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 985329d1b..1c26c3c19 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -140,4 +140,6 @@ def key_sequence_text_ctrl(control, interaction, delay): if not control.HasFocus(): control.SetFocus() for char in interaction.sequence: + if char == '\b': + char = "Backspace" key_click(control, char, delay) diff --git a/traitsui/tests/editors/test_range_editor.py b/traitsui/tests/editors/test_range_editor.py index ddccbdfa7..10e9f28fc 100644 --- a/traitsui/tests/editors/test_range_editor.py +++ b/traitsui/tests/editors/test_range_editor.py @@ -50,7 +50,6 @@ def test_simple_editor_format_func(self): def test_custom_editor_format_func(self): self.check_range_enum_editor_format_func("custom") - @requires_toolkit([ToolkitName.qt]) def check_set_with_text(self, mode): model = RangeModel() view = View( @@ -80,39 +79,3 @@ def test_log_range_slider_editor_set_with_text(self): def test_range_text_editor_set_with_text(self): return self.check_set_with_text(mode='text') - - # There is a problem with KeySequence on wx. trying to include the key - # '\b' does not succesfully type a backspace. Instead EmulateKeyPress - # seems to literally type "\x08" which leads to errors. - @requires_toolkit([ToolkitName.wx]) - def check_set_with_text_wx(self, mode): - model = RangeModel() - view = View( - Item( - "value", - editor=RangeEditor(low=1, high=12, mode=mode) - ) - ) - tester = UITester() - with tester.create_ui(model, dict(view=view)) as ui: - number_field = tester.find_by_name(ui, "value") - text = number_field.locate(locator.WidgetType.textbox) - for _ in range(5): - text.perform(command.KeyClick("Backspace")) - text.perform(command.KeySequence("10")) - text.perform(command.KeyClick("Enter")) - displayed = text.inspect(query.DisplayedText()) - self.assertEqual(model.value, 10) - self.assertEqual(displayed, str(model.value)) - - def test_simple_slider_editor_set_with_text_wx(self): - return self.check_set_with_text_wx(mode='slider') - - def test_large_range_slider_editor_set_with_text_wx(self): - return self.check_set_with_text_wx(mode='xslider') - - def test_log_range_slider_editor_set_with_text_wx(self): - return self.check_set_with_text_wx(mode='logslider') - - def test_range_text_editor_set_with_text_wx(self): - return self.check_set_with_text_wx(mode='text') From c03ef468977f1e54be40f22aac4a10e7d1137b62 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 1 Sep 2020 09:21:47 -0500 Subject: [PATCH 58/60] renaming located_object_handlers as common_ui_targets --- .../qt4/{located_object_handlers.py => common_ui_targets.py} | 0 traitsui/testing/tester/qt4/default_registry.py | 4 ++-- traitsui/testing/tester/qt4/implementation/range_editor.py | 2 +- .../wx/{located_object_handlers.py => common_ui_targets.py} | 0 traitsui/testing/tester/wx/default_registry.py | 4 ++-- traitsui/testing/tester/wx/implementation/range_editor.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename traitsui/testing/tester/qt4/{located_object_handlers.py => common_ui_targets.py} (100%) rename traitsui/testing/tester/wx/{located_object_handlers.py => common_ui_targets.py} (100%) diff --git a/traitsui/testing/tester/qt4/located_object_handlers.py b/traitsui/testing/tester/qt4/common_ui_targets.py similarity index 100% rename from traitsui/testing/tester/qt4/located_object_handlers.py rename to traitsui/testing/tester/qt4/common_ui_targets.py diff --git a/traitsui/testing/tester/qt4/default_registry.py b/traitsui/testing/tester/qt4/default_registry.py index 6321c461b..689d02ebf 100644 --- a/traitsui/testing/tester/qt4/default_registry.py +++ b/traitsui/testing/tester/qt4/default_registry.py @@ -10,7 +10,7 @@ # from traitsui.testing.tester.registry import TargetRegistry -from traitsui.testing.tester.qt4 import located_object_handlers +from traitsui.testing.tester.qt4 import common_ui_targets from traitsui.testing.tester.qt4.implementation import ( button_editor, range_editor, @@ -29,7 +29,7 @@ def get_default_registry(): """ registry = TargetRegistry() - located_object_handlers.LocatedTextbox.register(registry) + common_ui_targets.LocatedTextbox.register(registry) # ButtonEditor button_editor.register(registry) diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index 31a64593a..82d59dcad 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -17,7 +17,7 @@ ) from traitsui.testing.tester import locator -from traitsui.testing.tester.qt4.located_object_handlers import LocatedTextbox +from traitsui.testing.tester.qt4.common_ui_targets import LocatedTextbox def resolve_location_slider(wrapper, location): diff --git a/traitsui/testing/tester/wx/located_object_handlers.py b/traitsui/testing/tester/wx/common_ui_targets.py similarity index 100% rename from traitsui/testing/tester/wx/located_object_handlers.py rename to traitsui/testing/tester/wx/common_ui_targets.py diff --git a/traitsui/testing/tester/wx/default_registry.py b/traitsui/testing/tester/wx/default_registry.py index bed4e76d5..e49ae8b6c 100644 --- a/traitsui/testing/tester/wx/default_registry.py +++ b/traitsui/testing/tester/wx/default_registry.py @@ -10,7 +10,7 @@ # from traitsui.testing.tester.registry import TargetRegistry -from traitsui.testing.tester.wx import located_object_handlers +from traitsui.testing.tester.wx import common_ui_targets from traitsui.testing.tester.wx.implementation import ( button_editor, range_editor, @@ -29,7 +29,7 @@ def get_default_registry(): """ registry = TargetRegistry() - located_object_handlers.LocatedTextbox.register(registry) + common_ui_targets.LocatedTextbox.register(registry) # ButtonEditor button_editor.register(registry) diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index efa850e44..1c5260563 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -16,7 +16,7 @@ ) from traitsui.testing.tester import locator -from traitsui.testing.tester.wx.located_object_handlers import LocatedTextbox +from traitsui.testing.tester.wx.common_ui_targets import LocatedTextbox def resolve_location_slider(wrapper, location): From f8b4b9c576578c153de3d0626113bba31eb8d638 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 1 Sep 2020 09:40:20 -0500 Subject: [PATCH 59/60] more suggested changes --- traitsui/testing/tester/locator.py | 2 +- traitsui/testing/tester/qt4/implementation/range_editor.py | 5 ----- traitsui/testing/tester/wx/helpers.py | 4 +--- traitsui/testing/tester/wx/implementation/range_editor.py | 5 ----- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/traitsui/testing/tester/locator.py b/traitsui/testing/tester/locator.py index 3bb001dd6..dcaece195 100644 --- a/traitsui/testing/tester/locator.py +++ b/traitsui/testing/tester/locator.py @@ -44,7 +44,7 @@ class WidgetType(enum.Enum): contain many sub-widgets (e.g. a textbox, slider, tabs, buttons, etc.). For example when working with a range editor, one could call - tester.find_by_name(ui, "ranged_number").locate(locator.WidgetType.textbox) + ``tester.find_by_name(ui, "ranged_number").locate(locator.WidgetType.textbox)`` """ # A textbox within a UI diff --git a/traitsui/testing/tester/qt4/implementation/range_editor.py b/traitsui/testing/tester/qt4/implementation/range_editor.py index 82d59dcad..8554943d9 100644 --- a/traitsui/testing/tester/qt4/implementation/range_editor.py +++ b/traitsui/testing/tester/qt4/implementation/range_editor.py @@ -62,11 +62,6 @@ def resolve_location_range_text(wrapper, location): if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) - if location in [locator.WidgetType.slider]: - raise NotImplementedError( - f"Logic for interacting with the {location}" - " has not been implemented." - ) raise ValueError( f"Unable to resolve {location} on {wrapper.target}." " Currently supported: {locator.WidgetType.textbox}" diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 1c26c3c19..98936cb62 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -43,7 +43,7 @@ def key_click(widget, key, delay=0): else: wx.MilliSleep(delay) key_event = wx.KeyEvent(wx.wxEVT_CHAR) - key_event.SetUnicodeKey(KEY) + key_event.SetKeyCode(KEY) widget.EmulateKeyPress(key_event) else: wx.MilliSleep(delay) @@ -140,6 +140,4 @@ def key_sequence_text_ctrl(control, interaction, delay): if not control.HasFocus(): control.SetFocus() for char in interaction.sequence: - if char == '\b': - char = "Backspace" key_click(control, char, delay) diff --git a/traitsui/testing/tester/wx/implementation/range_editor.py b/traitsui/testing/tester/wx/implementation/range_editor.py index 1c5260563..cf1a29119 100644 --- a/traitsui/testing/tester/wx/implementation/range_editor.py +++ b/traitsui/testing/tester/wx/implementation/range_editor.py @@ -61,11 +61,6 @@ def resolve_location_range_text(wrapper, location): if location == locator.WidgetType.textbox: return LocatedTextbox(textbox=wrapper.target.control) - if location in [locator.WidgetType.slider]: - raise NotImplementedError( - f"Logic for interacting with the {location}" - " has not been implemented." - ) raise ValueError( f"Unable to resolve {location} on {wrapper.target}." " Currently supported: {locator.WidgetType.textbox}" From 4f723e14fc17a9143ac6e99e36f535a174ded977 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 1 Sep 2020 11:06:22 -0500 Subject: [PATCH 60/60] revert SetKeyCode change and remove '\b' in test --- traitsui/testing/tester/wx/helpers.py | 2 +- traitsui/tests/editors/test_range_editor.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/traitsui/testing/tester/wx/helpers.py b/traitsui/testing/tester/wx/helpers.py index 98936cb62..985329d1b 100644 --- a/traitsui/testing/tester/wx/helpers.py +++ b/traitsui/testing/tester/wx/helpers.py @@ -43,7 +43,7 @@ def key_click(widget, key, delay=0): else: wx.MilliSleep(delay) key_event = wx.KeyEvent(wx.wxEVT_CHAR) - key_event.SetKeyCode(KEY) + key_event.SetUnicodeKey(KEY) widget.EmulateKeyPress(key_event) else: wx.MilliSleep(delay) diff --git a/traitsui/tests/editors/test_range_editor.py b/traitsui/tests/editors/test_range_editor.py index 10e9f28fc..e03284b76 100644 --- a/traitsui/tests/editors/test_range_editor.py +++ b/traitsui/tests/editors/test_range_editor.py @@ -62,7 +62,9 @@ def check_set_with_text(self, mode): with tester.create_ui(model, dict(view=view)) as ui: number_field = tester.find_by_name(ui, "value") text = number_field.locate(locator.WidgetType.textbox) - text.perform(command.KeySequence("\b\b\b\b\b4")) + for _ in range(5): + text.perform(command.KeyClick("Backspace")) + text.perform(command.KeyClick("4")) text.perform(command.KeyClick("Enter")) displayed = text.inspect(query.DisplayedText()) self.assertEqual(model.value, 4)