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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions traitsui/testing/tester/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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!
#
""" This module contains functions used by toolkit specific implementation for
normalizing differences among toolkits (Qt and Wx).
"""


def check_key_compat(key):
""" Check if the given key is a unicode character within the range of
values currently supported for emulating key sequences on both Qt and Wx
textboxes.

Parameters
----------
key : str
A unicode character

Raises
------
ValueError
If the unicode character is not within the supported range of values.
"""
# Support for more characters can be added when there are needs.
if ord(key) < 32 or ord(key) >= 127:
raise ValueError(
f"Key {key!r} is currently not supported. "
f"Supported characters between code point 32 - 126."
)
2 changes: 1 addition & 1 deletion traitsui/testing/tester/qt4/common_ui_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def register(cls, registry):
"""
handlers = [
(command.KeySequence,
(lambda wrapper, interaction: helpers.key_sequence_qwidget(
(lambda wrapper, interaction: helpers.key_sequence_textbox(
wrapper.target.textbox, interaction, wrapper.delay))),
(command.KeyClick,
(lambda wrapper, interaction: helpers.key_click_qwidget(
Expand Down
25 changes: 25 additions & 0 deletions traitsui/testing/tester/qt4/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pyface.qt import QtCore, QtGui
from pyface.qt.QtTest import QTest

from traitsui.testing.tester.compat import check_key_compat
from traitsui.testing.tester.exceptions import Disabled
from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP

Expand Down Expand Up @@ -110,6 +111,30 @@ def key_sequence_qwidget(control, interaction, delay):
QTest.keyClicks(control, interaction.sequence, delay=delay)


def key_sequence_textbox(control, interaction, delay):
""" Performs simulated typing of a sequence of keys on a widget that is
a textbox. The keys are restricted to values also supported for testing
wx.TextCtrl.

Parameters
----------
control : QWidget
The Qt widget intended to hold text for editing.
e.g. QLineEdit and QTextEdit
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.
"""
for key in interaction.sequence:
check_key_compat(key)
Comment on lines +131 to +132
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For future reference, if we did not check and try to use QTest with a character e.g. "ಶ", then Qt will throw this error:

ASSERT: "false" in file /Users/builder/workspace/Buildsystem/test/pisi/tmp/Qt-5.12.6-5/work/qt/qtbase/src/testlib/qasciikey.cpp, line 228
Abort trap: 6

If there are needs to support a wider range of characters, then we would need to do something else. Depending on the use case, we may manually edit the text and fire the appropriate Qt event used by the editor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test test_key_sequence_unsupported_key demonstrates the above error if the check was not added.

if not control.hasFocus():
key_click(widget=control, key="End", delay=0)
key_sequence_qwidget(control=control, interaction=interaction, delay=delay)


def key_click_qwidget(control, interaction, delay):
""" Performs simulated typing of a key on the given widget after a delay.

Expand Down
2 changes: 1 addition & 1 deletion traitsui/testing/tester/qt4/implementation/text_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def register(registry):

handlers = [
(command.KeySequence,
(lambda wrapper, interaction: helpers.key_sequence_qwidget(
(lambda wrapper, interaction: helpers.key_sequence_textbox(
wrapper.target.control, interaction, wrapper.delay))),
(command.KeyClick,
(lambda wrapper, interaction: helpers.key_click_qwidget(
Expand Down
81 changes: 81 additions & 0 deletions traitsui/testing/tester/qt4/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,87 @@ def test_key_sequence(self):
0)
self.assertEqual(textbox.text(), "")

def test_key_sequence_textbox_with_unicode(self):
for code in range(32, 127):
with self.subTest(code=code, word=chr(code)):
textbox = QtGui.QLineEdit()
change_slot = mock.Mock()
textbox.textChanged.connect(change_slot)

# when
helpers.key_sequence_textbox(
textbox,
command.KeySequence(chr(code) * 3),
delay=0,
)

# then
self.assertEqual(textbox.text(), chr(code) * 3)
self.assertEqual(change_slot.call_count, 3)

def test_key_sequence_unsupported_key(self):
textbox = QtGui.QLineEdit()

with self.assertRaises(ValueError) as exception_context:
# QTest does not support this character.
helpers.key_sequence_textbox(
textbox,
command.KeySequence(chr(31)),
delay=0,
)

self.assertIn(
"is currently not supported.",
str(exception_context.exception),
)

def test_key_sequence_backspace_character(self):
# Qt does convert backspace character to the backspace key
# But we disallow it for now to be consistent with wx.
textbox = QtGui.QLineEdit()

with self.assertRaises(ValueError) as exception_context:
helpers.key_sequence_textbox(
textbox,
command.KeySequence("\b"),
delay=0,
)

self.assertIn(
"is currently not supported.",
str(exception_context.exception),
)

def test_key_sequence_insert_point_qlineedit(self):
textbox = QtGui.QLineEdit()
textbox.setText("123")

# when
helpers.key_sequence_textbox(
textbox,
command.KeySequence("abc"),
delay=0,
)

# then
self.assertEqual(textbox.text(), "123abc")

def test_key_sequence_insert_point_qtextedit(self):
# The default insertion point moved to the end to be consistent
# with QLineEdit
textbox = QtGui.QTextEdit()
textbox.setText("123")

# when
helpers.key_sequence_textbox(
textbox,
command.KeySequence("abc"),
delay=0,
)

# then
self.assertEqual(textbox.toPlainText(), "123abc")

def test_key_sequence_disabled(self):
textbox = QtGui.QLineEdit()
textbox.setEnabled(False)
Expand Down
9 changes: 8 additions & 1 deletion traitsui/testing/tester/wx/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import wx

from traitsui.testing.tester.compat import check_key_compat
from traitsui.testing.tester.exceptions import Disabled
from traitsui.wx.key_event_to_name import key_map as _KEY_MAP

Expand Down Expand Up @@ -135,9 +136,15 @@ def key_sequence_text_ctrl(control, interaction, delay):
Time delay (in ms) in which each key click in the sequence will be
performed.
"""
# fail early
for char in interaction.sequence:
check_key_compat(char)

if not control.IsEditable():
raise Disabled("{!r} is disabled.".format(control))
if not control.HasFocus():
control.SetFocus()
control.SetInsertionPointEnd()
for char in interaction.sequence:
key_click(control, char, delay)
wx.MilliSleep(delay)
control.WriteText(char)
41 changes: 40 additions & 1 deletion traitsui/testing/tester/wx/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,50 @@ def test_mouse_click_disabled_button(self):
self.assertEqual(handler.call_count, 0)

def test_key_sequence(self):
# The insertion point is moved to the end
textbox = wx.TextCtrl(self.frame)
textbox.SetValue("123")
handler = mock.Mock()
textbox.Bind(wx.EVT_TEXT, handler)

helpers.key_sequence_text_ctrl(textbox, command.KeySequence("abc"), 0)

self.assertEqual(textbox.Value, "abc")
self.assertEqual(textbox.GetValue(), "123abc")
self.assertEqual(handler.call_count, 3)

def test_key_sequence_with_unicode(self):
handler = mock.Mock()
textbox = wx.TextCtrl(self.frame)
textbox.Bind(wx.EVT_TEXT, handler)
# This range is supported by Qt
for code in range(32, 127):
with self.subTest(code=code, word=chr(code)):
textbox.Clear()
handler.reset_mock()

# when
helpers.key_sequence_text_ctrl(
textbox,
command.KeySequence(chr(code) * 3),
delay=0,
)

# then
self.assertEqual(textbox.Value, chr(code) * 3)
self.assertEqual(handler.call_count, 3)

def test_key_sequence_with_backspace_unsupported(self):
textbox = wx.TextCtrl(self.frame)

with self.assertRaises(ValueError) as exception_context:
helpers.key_sequence_text_ctrl(
textbox, command.KeySequence("\b"), 0
)

self.assertIn(
"is currently not supported.",
str(exception_context.exception),
)

def test_key_sequence_disabled(self):
textbox = wx.TextCtrl(self.frame)
Expand Down