Commit 223dd8d7 authored by Terry Jan Reedy's avatar Terry Jan Reedy

#21940: Add unittest for WidgetRedirector. Initial patch by Saimadhav Heblikar.

parent 737c34fa
...@@ -3,19 +3,21 @@ from tkinter import TclError ...@@ -3,19 +3,21 @@ from tkinter import TclError
class WidgetRedirector: class WidgetRedirector:
"""Support for redirecting arbitrary widget subcommands. """Support for redirecting arbitrary widget subcommands.
Some Tk operations don't normally pass through Tkinter. For example, if a Some Tk operations don't normally pass through tkinter. For example, if a
character is inserted into a Text widget by pressing a key, a default Tk character is inserted into a Text widget by pressing a key, a default Tk
binding to the widget's 'insert' operation is activated, and the Tk library binding to the widget's 'insert' operation is activated, and the Tk library
processes the insert without calling back into Tkinter. processes the insert without calling back into tkinter.
Although a binding to <Key> could be made via Tkinter, what we really want Although a binding to <Key> could be made via tkinter, what we really want
to do is to hook the Tk 'insert' operation itself. to do is to hook the Tk 'insert' operation itself. For one thing, we want
a text.insert call in idle code to have the same effect as a key press.
When a widget is instantiated, a Tcl command is created whose name is the When a widget is instantiated, a Tcl command is created whose name is the
same as the pathname widget._w. This command is used to invoke the various same as the pathname widget._w. This command is used to invoke the various
widget operations, e.g. insert (for a Text widget). We are going to hook widget operations, e.g. insert (for a Text widget). We are going to hook
this command and provide a facility ('register') to intercept the widget this command and provide a facility ('register') to intercept the widget
operation. operation. We will also intercept method calls on the tkinter class
instance that represents the tk widget.
In IDLE, WidgetRedirector is used in Percolator to intercept Text In IDLE, WidgetRedirector is used in Percolator to intercept Text
commands. The function being registered provides access to the top commands. The function being registered provides access to the top
...@@ -64,9 +66,13 @@ class WidgetRedirector: ...@@ -64,9 +66,13 @@ class WidgetRedirector:
def register(self, operation, function): def register(self, operation, function):
'''Return OriginalCommand(operation) after registering function. '''Return OriginalCommand(operation) after registering function.
Registration adds an instance function attribute that masks the Registration adds an operation: function pair to ._operations.
class instance method attribute. If a second function is It also adds an widget function attribute that masks the tkinter
registered for the same operation, the first function is replaced. class instance method. Method masking operates independently
from command dispatch.
If a second function is registered for the same operation, the
first function is replaced in both places.
''' '''
self._operations[operation] = function self._operations[operation] = function
setattr(self.widget, operation, function) setattr(self.widget, operation, function)
...@@ -80,8 +86,10 @@ class WidgetRedirector: ...@@ -80,8 +86,10 @@ class WidgetRedirector:
if operation in self._operations: if operation in self._operations:
function = self._operations[operation] function = self._operations[operation]
del self._operations[operation] del self._operations[operation]
if hasattr(self.widget, operation): try:
delattr(self.widget, operation) delattr(self.widget, operation)
except AttributeError:
pass
return function return function
else: else:
return None return None
...@@ -160,8 +168,7 @@ def _widget_redirector(parent): # htest # ...@@ -160,8 +168,7 @@ def _widget_redirector(parent): # htest #
if __name__ == "__main__": if __name__ == "__main__":
import unittest import unittest
## unittest.main('idlelib.idle_test.test_widgetredir', unittest.main('idlelib.idle_test.test_widgetredir',
## verbosity=2, exit=False) verbosity=2, exit=False)
from idlelib.idle_test.htest import run from idlelib.idle_test.htest import run
run(_widget_redirector) run(_widget_redirector)
...@@ -26,6 +26,9 @@ class Func: ...@@ -26,6 +26,9 @@ class Func:
self.called = True self.called = True
self.args = args self.args = args
self.kwds = kwds self.kwds = kwds
if isinstance(self.result, BaseException):
raise self.result
else:
return self.result return self.result
......
"""Unittest for idlelib.WidgetRedirector
100% coverage
"""
from test.support import requires
import unittest
from idlelib.idle_test.mock_idle import Func
from tkinter import Tk, Text, TclError
from idlelib.WidgetRedirector import WidgetRedirector
class InitCloseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.tk = Tk()
cls.text = Text(cls.tk)
@classmethod
def tearDownClass(cls):
cls.text.destroy()
cls.tk.destroy()
del cls.text, cls.tk
def test_init(self):
redir = WidgetRedirector(self.text)
self.assertEqual(redir.widget, self.text)
self.assertEqual(redir.tk, self.text.tk)
self.assertRaises(TclError, WidgetRedirector, self.text)
redir.close() # restore self.tk, self.text
def test_close(self):
redir = WidgetRedirector(self.text)
redir.register('insert', Func)
redir.close()
self.assertEqual(redir._operations, {})
self.assertFalse(hasattr(self.text, 'widget'))
class WidgetRedirectorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.tk = Tk()
cls.text = Text(cls.tk)
@classmethod
def tearDownClass(cls):
cls.text.destroy()
cls.tk.destroy()
del cls.text, cls.tk
def setUp(self):
self.redir = WidgetRedirector(self.text)
self.func = Func()
self.orig_insert = self.redir.register('insert', self.func)
self.text.insert('insert', 'asdf') # leaves self.text empty
def tearDown(self):
self.text.delete('1.0', 'end')
self.redir.close()
def test_repr(self): # partly for 100% coverage
self.assertIn('Redirector', repr(self.redir))
self.assertIn('Original', repr(self.orig_insert))
def test_register(self):
self.assertEqual(self.text.get('1.0', 'end'), '\n')
self.assertEqual(self.func.args, ('insert', 'asdf'))
self.assertIn('insert', self.redir._operations)
self.assertIn('insert', self.text.__dict__)
self.assertEqual(self.text.insert, self.func)
def test_original_command(self):
self.assertEqual(self.orig_insert.operation, 'insert')
self.assertEqual(self.orig_insert.tk_call, self.text.tk.call)
self.orig_insert('insert', 'asdf')
self.assertEqual(self.text.get('1.0', 'end'), 'asdf\n')
def test_unregister(self):
self.assertIsNone(self.redir.unregister('invalid operation name'))
self.assertEqual(self.redir.unregister('insert'), self.func)
self.assertNotIn('insert', self.redir._operations)
self.assertNotIn('insert', self.text.__dict__)
def test_unregister_no_attribute(self):
del self.text.insert
self.assertEqual(self.redir.unregister('insert'), self.func)
def test_dispatch_intercept(self):
self.func.__init__(True)
self.assertTrue(self.redir.dispatch('insert', False))
self.assertFalse(self.func.args[0])
def test_dispatch_bypass(self):
self.orig_insert('insert', 'asdf')
# tk.call returns '' where Python would return None
self.assertEqual(self.redir.dispatch('delete', '1.0', 'end'), '')
self.assertEqual(self.text.get('1.0', 'end'), '\n')
def test_dispatch_error(self):
self.func.__init__(TclError())
self.assertEqual(self.redir.dispatch('insert', False), '')
self.assertEqual(self.redir.dispatch('invalid'), '')
def test_command_dispatch(self):
# Test that .__init__ causes redirection of tk calls
# through redir.dispatch
self.tk.call(self.text._w, 'insert', 'hello')
self.assertEqual(self.func.args, ('hello',))
self.assertEqual(self.text.get('1.0', 'end'), '\n')
# Ensure that called through redir .dispatch and not through
# self.text.insert by having mock raise TclError.
self.func.__init__(TclError())
self.assertEqual(self.tk.call(self.text._w, 'insert', 'boo'), '')
if __name__ == '__main__':
unittest.main(verbosity=2)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment