Skip to content

Commit

Permalink
Merge branch 'range_editor_bug'
Browse files Browse the repository at this point in the history
  • Loading branch information
Ilan Schnell committed Dec 21, 2011
2 parents ce446c5 + dc145b2 commit 9cbfee2
Show file tree
Hide file tree
Showing 7 changed files with 530 additions and 4 deletions.
Empty file added traitsui/tests/__init__.py
Empty file.
137 changes: 137 additions & 0 deletions traitsui/tests/_tools.py
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
143 changes: 143 additions & 0 deletions traitsui/tests/test_range_editor_spinner.py
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
61 changes: 61 additions & 0 deletions traitsui/tests/test_range_editor_text.py
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
Loading

0 comments on commit 9cbfee2

Please sign in to comment.