1
"""Objects that help unit-test pyGTK applications."""
8
from guitest.utils import GuiTestHelperMixin, DoctestHelper
9
from guitest.state import guistate
13
# Stubbed GTK global functions
16
def pygtk_require(version):
21
mainhook = guistate.main
23
raise ValueError("mainhook not specified!")
25
guistate.main = None # try to avoid infinite recursion
38
def gtk_main_iteration(block=True):
43
# Some mutilated GTK classes to suppress side effects
47
class _InvisibleWidgetMixin(object):
48
"""A mixin that can be used to try to stop widgets from appearing onscreen.
50
Its attribute `_visible` (None by default) is set to True if the widget's
51
show() method or friends are called, False if hide() or friends are called.
67
class gtk_Window(_InvisibleWidgetMixin, gtk.Window):
71
class _StubbedDialogMixin(_InvisibleWidgetMixin):
72
"""A mixin that simulates a response to a dialog.
74
Normally when a dialog's run() method is called, the application will
75
block. This mixin runs the dialog handler provided in the unit test
76
code instead of waiting for input from the user.
80
"""Run the dialog and return a user-specified value.
82
Invokes callable guistate.dlg_handler with the dialog instance
83
as a single argument and returns the handler's return value as
86
assert guistate.dlg_handler, 'dlg_handler not set'
87
handler = guistate.dlg_handler
88
guistate.dlg_handler = None
89
response = handler(self)
90
assert response is not None, 'dlg_handler returned None'
94
# Find and override out gtk.Dialog and all its subclasses
100
_gtk_attrs.remove('_gobject') # avoid a deprecation warning
102
pass # the attribute may be removed in the future
104
for attr_name in _gtk_attrs:
106
attr = getattr(gtk, attr_name)
107
if issubclass(attr, gtk.Dialog):
108
class_name = 'gtk_' + attr_name
109
stubbedclass = type(class_name, (_StubbedDialogMixin, attr), {})
110
dialog_overrides['gtk.' + attr_name] = stubbedclass
112
pass # most probably attr is not a class
119
class _ProxyMixin(object):
120
"""Mixin for use in proxy classes.
122
This mixin is used in the glade stubs (see gtk_glade_XML).
124
Note that use of this mixin breaks isinstance() for proxied widgets.
125
You're better off not trying to add them to other containers by hand, etc.
130
def __init__(self, obj):
133
def __getattr__(self, name):
134
return getattr(self._obj, name)
137
def _createProxy(stubbedclass, overrides):
138
"""Create a proxy class that overrides methods listed in 'overrides'.
140
A convenience function used in gtk_glade_XML.
143
for name in overrides:
144
cls_dict[name] = getattr(stubbedclass, name).im_func
145
return type(stubbedclass.__name__, (_ProxyMixin, ), cls_dict)
148
class gtk_glade_XML(gtk.glade.XML):
149
"""A stub for the gtk.glade.XML object."""
151
__super_get_widget = gtk.glade.XML.get_widget
155
_DialogProxy = _createProxy(dialog_overrides['gtk.Dialog'],
156
['run', 'show', 'show_all'])
157
_WindowProxy = _createProxy(gtk_Window, ['show', 'show_all'])
159
def get_widget(self, name):
160
widget = self.__super_get_widget(name)
161
if isinstance(widget, self._Dialog):
162
return self._DialogProxy(widget)
163
elif isinstance(widget, self._Window):
164
return self._WindowProxy(widget)
170
# Unit-test and doctest helpers.
173
class GtkTestHelperMixin(GuiTestHelperMixin):
175
toolkit_overrides = {'gtk.main': gtk_main,
176
'gtk.main_quit': gtk_main_quit,
177
'gtk.main_level': gtk_main_level,
178
'gtk.mainloop': gtk_main,
179
'gtk.mainquit': gtk_main_quit,
180
'gtk.main_iteration': gtk_main_iteration,
181
'gtk.main_iteration_do': gtk_main_iteration,
182
'gtk.Window': gtk_Window,
183
'gtk.glade.XML': gtk_glade_XML,
184
'pygtk.require': pygtk_require}
185
toolkit_overrides.update(dialog_overrides)
189
class GtkTestCase(GtkTestHelperMixin, unittest.TestCase):
190
"""A convenience TestCase for use in GTK application unit tests."""
193
doctesthelper = DoctestHelper(GtkTestHelperMixin)
195
setUp_param = doctesthelper.setUp_param
196
setUp = doctesthelper.setUp
197
tearDown = doctesthelper.tearDown