forked from enthought/traitsui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
530 additions
and
4 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
from functools import partial | ||
from contextlib import contextmanager | ||
import nose | ||
|
||
import sys | ||
import traceback | ||
|
||
from traits.etsconfig.api import ETSConfig | ||
|
||
# ######### Testing tools | ||
|
||
@contextmanager | ||
def store_exceptions_on_all_threads(): | ||
"""Context manager that captures all exceptions, even those coming from | ||
the UI thread. On exit, the first exception is raised (if any). | ||
""" | ||
|
||
exceptions = [] | ||
|
||
def excepthook(type, value, tb): | ||
exceptions.append(value) | ||
message = 'Uncaught exception:\n' | ||
message += ''.join(traceback.format_exception(type, value, tb)) | ||
print message | ||
|
||
try: | ||
sys.excepthook = excepthook | ||
yield | ||
finally: | ||
if len(exceptions) > 0: | ||
raise exceptions[0] | ||
sys.excepthook = sys.__excepthook__ | ||
|
||
|
||
def skip_if_not_backend(test_func, backend_name=''): | ||
"""Decorator that skip tests if the backend is not the desired one.""" | ||
|
||
if ETSConfig.toolkit != backend_name: | ||
# preserve original name so that it appears in the report | ||
orig_name = test_func.__name__ | ||
def test_func(): | ||
raise nose.SkipTest | ||
test_func.__name__ = orig_name | ||
|
||
return test_func | ||
|
||
|
||
#: Test decorator: Skip test if backend is not 'wx' | ||
skip_if_not_wx = partial(skip_if_not_backend, backend_name='wx') | ||
|
||
#: Test decorator: Skip test if backend is not 'qt4' | ||
skip_if_not_qt4 = partial(skip_if_not_backend, backend_name='qt4') | ||
|
||
|
||
def count_calls(func): | ||
"""Decorator that stores the number of times a function is called. | ||
The counter is stored in func._n_counts. | ||
""" | ||
|
||
def wrapped(*args, **kwargs): | ||
wrapped._n_calls += 1 | ||
return func(*args, **kwargs) | ||
|
||
wrapped._n_calls = 0 | ||
|
||
return wrapped | ||
|
||
|
||
# ######### Utility tools to test on both qt4 and wx | ||
|
||
def get_children(node): | ||
if ETSConfig.toolkit == 'wx': | ||
return node.GetChildren() | ||
else: | ||
return node.children() | ||
|
||
|
||
# ######### Debug tools | ||
|
||
def apply_on_children(func, node, _level=0): | ||
"""Print the result of applying a function on `node` and its children. | ||
""" | ||
print '-'*_level + str(node) | ||
print ' '*_level + str(func(node)) + '\n' | ||
for child in get_children(node): | ||
apply_on_children(func, child, _level+1) | ||
|
||
|
||
def wx_print_names(node): | ||
"""Print the name and id of `node` and its children. | ||
""" | ||
apply_on_children(lambda n: (n.GetName(), n.GetId()), node) | ||
|
||
|
||
def qt_print_names(node): | ||
"""Print the name of `node` and its children. | ||
""" | ||
apply_on_children(lambda n: n.objectName(), node) | ||
|
||
|
||
def wx_announce_when_destroyed(node): | ||
"""Prints a message when `node` is destroyed. | ||
Use as: | ||
>>> ui = xxx.edit_traits() | ||
>>> apply_on_children(wx_announce_when_destroyed, ui.control) | ||
""" | ||
|
||
_destroy_method = node.Destroy | ||
|
||
def destroy_wrapped(): | ||
print 'Destroying:', node | ||
#print 'Stack is' | ||
#traceback.print_stack() | ||
_destroy_method() | ||
print 'Destroyed:', node | ||
|
||
node.Destroy = destroy_wrapped | ||
return 'Node {} decorated'.format(node.GetName()) | ||
|
||
|
||
def wx_find_event_by_number(evt_num): | ||
"""Find all wx event names that correspond to a ceratin event number. | ||
Example: | ||
>>> wx_find_event_by_number(10010) | ||
['wxEVT_COMMAND_MENU_SELECTED', 'wxEVT_COMMAND_TOOL_CLICKED'] | ||
""" | ||
|
||
import wx | ||
possible = [attr for attr in dir(wx) | ||
if attr.startswith('wxEVT') and getattr(wx, attr) == evt_num] | ||
|
||
return possible |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
""" | ||
Test case for bug (wx, Max OS X) | ||
Editing the text part of a spin control box and pressing the OK button | ||
without de-focusing raises an AttributeError | ||
Traceback (most recent call last): | ||
File "ETS/traitsui/traitsui/wx/range_editor.py", line 783, in update_object | ||
self.value = self.control.GetValue() | ||
AttributeError: 'NoneType' object has no attribute 'GetValue' | ||
""" | ||
|
||
from traits.has_traits import HasTraits | ||
from traits.trait_types import Int | ||
from traitsui.item import Item | ||
from traitsui.view import View | ||
from traitsui.editors.range_editor import RangeEditor | ||
|
||
from _tools import * | ||
|
||
|
||
class NumberWithSpinnerEditor(HasTraits): | ||
"""Dialog containing a RangeEditor in 'spinner' mode for an Int. | ||
""" | ||
|
||
number = Int | ||
|
||
traits_view = View( | ||
Item(label="Enter 4, then press OK without defocusing"), | ||
Item('number', editor=RangeEditor(low=3, high=8, mode='spinner')), | ||
buttons = ['OK'] | ||
) | ||
|
||
|
||
@skip_if_not_wx | ||
def test_wx_spin_control_editing_should_not_crash(): | ||
# Bug: when editing the text part of a spin control box, pressing | ||
# the OK button raises an AttributeError on Mac OS X | ||
|
||
import wx | ||
|
||
try: | ||
with store_exceptions_on_all_threads(): | ||
num = NumberWithSpinnerEditor() | ||
ui = num.edit_traits() | ||
|
||
# the following is equivalent to clicking in the text control of the | ||
# range editor, enter a number, and clicking ok without defocusing | ||
|
||
# SpinCtrl object | ||
spin = ui.control.FindWindowByName('wxSpinCtrl') | ||
spin.SetFocusFromKbd() | ||
|
||
# on Windows, a wxSpinCtrl does not have children, and we cannot do | ||
# the more fine-grained testing below | ||
if len(spin.GetChildren()) == 0: | ||
spin.SetValueString('4') | ||
else: | ||
# TextCtrl object of the spin control | ||
spintxt = spin.FindWindowByName('text') | ||
spintxt.SetValue('4') | ||
|
||
# press the OK button and close the dialog | ||
okbutton = ui.control.FindWindowByName('button') | ||
click_event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, | ||
okbutton.GetId()) | ||
okbutton.ProcessEvent(click_event) | ||
except AttributeError: | ||
# if all went well, we should not be here | ||
assert False, "AttributeError raised" | ||
|
||
|
||
|
||
@skip_if_not_wx | ||
def test_wx_spin_control_editing_does_not_update(): | ||
# Bug: when editing the text part of a spin control box, pressing | ||
# the OK button does not update the value of the HasTraits class | ||
# on Mac OS X | ||
|
||
import wx | ||
|
||
with store_exceptions_on_all_threads(): | ||
num = NumberWithSpinnerEditor() | ||
ui = num.edit_traits() | ||
|
||
# the following is equivalent to clicking in the text control of the | ||
# range editor, enter a number, and clicking ok without defocusing | ||
|
||
# SpinCtrl object | ||
spin = ui.control.FindWindowByName('wxSpinCtrl') | ||
spin.SetFocusFromKbd() | ||
|
||
# on Windows, a wxSpinCtrl does not have children, and we cannot do | ||
# the more fine-grained testing below | ||
if len(spin.GetChildren()) == 0: | ||
spin.SetValueString('4') | ||
else: | ||
# TextCtrl object of the spin control | ||
spintxt = spin.FindWindowByName('text') | ||
spintxt.SetValue('4') | ||
|
||
# press the OK button and close the dialog | ||
okbutton = ui.control.FindWindowByName('button') | ||
click_event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, | ||
okbutton.GetId()) | ||
okbutton.ProcessEvent(click_event) | ||
|
||
# if all went well, the number traits has been updated and its value is 4 | ||
assert num.number == 4 | ||
|
||
|
||
@skip_if_not_qt4 | ||
def test_qt_spin_control_editing(): | ||
# Behavior: when editing the text part of a spin control box, pressing | ||
# the OK button updates the value of the HasTraits class | ||
|
||
from pyface import qt | ||
|
||
with store_exceptions_on_all_threads(): | ||
num = NumberWithSpinnerEditor() | ||
ui = num.edit_traits() | ||
|
||
# the following is equivalent to clicking in the text control of the | ||
# range editor, enter a number, and clicking ok without defocusing | ||
|
||
# text element inside the spin control | ||
lineedit = ui.control.findChild(qt.QtGui.QLineEdit) | ||
lineedit.setFocus() | ||
lineedit.setText('4') | ||
|
||
# press the OK button and close the dialog | ||
okb = ui.control.findChild(qt.QtGui.QPushButton) | ||
okb.click() | ||
|
||
# if all went well, the number traits has been updated and its value is 4 | ||
assert num.number == 4 | ||
|
||
|
||
if __name__ == '__main__': | ||
# Executing the file opens the dialog for manual testing | ||
num = NumberWithSpinnerEditor() | ||
num.configure_traits() | ||
print num.number |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
""" | ||
Test case for bug (wx, Max OS X) | ||
A RangeEditor in mode 'text' for an Int allows values out of range. | ||
""" | ||
|
||
from traits.has_traits import HasTraits | ||
from traits.trait_types import Int | ||
from traitsui.item import Item | ||
from traitsui.view import View | ||
from traitsui.editors.range_editor import RangeEditor | ||
|
||
from _tools import * | ||
|
||
|
||
class NumberWithTextEditor(HasTraits): | ||
"""Dialog containing a RangeEditor in 'spinner' mode for an Int. | ||
""" | ||
|
||
number = Int | ||
|
||
traits_view = View( | ||
Item(label="Range should be 3 to 8. Enter 1, then press OK"), | ||
Item('number', editor=RangeEditor(low=3, high=8, mode='text')), | ||
buttons = ['OK'] | ||
) | ||
|
||
|
||
@skip_if_not_wx | ||
def test_wx_spin_control_editing(): | ||
# behavior: when editing the text part of a spin control box, pressing | ||
# the OK button should update the value of the HasTraits class | ||
# (tests a bug where this fails with an AttributeError) | ||
|
||
import wx | ||
|
||
with store_exceptions_on_all_threads(): | ||
num = NumberWithTextEditor() | ||
ui = num.edit_traits() | ||
|
||
# the following is equivalent to setting the text in the text control, | ||
# then pressing OK | ||
|
||
textctrl = ui.control.FindWindowByName('text') | ||
textctrl.SetValue('1') | ||
|
||
# press the OK button and close the dialog | ||
okbutton = ui.control.FindWindowByName('button') | ||
click_event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, | ||
okbutton.GetId()) | ||
okbutton.ProcessEvent(click_event) | ||
|
||
# the number traits should be between 3 and 8 | ||
assert num.number >= 3 and num.number <=8 | ||
|
||
|
||
if __name__ == '__main__': | ||
# Executing the file opens the dialog for manual testing | ||
num = NumberWithTextEditor() | ||
num.configure_traits() | ||
print num.number |
Oops, something went wrong.