diff -r 220d5fdbe22e Lib/idlelib/WidgetRedirector.py --- a/Lib/idlelib/WidgetRedirector.py Thu Jul 10 01:16:49 2014 -0400 +++ b/Lib/idlelib/WidgetRedirector.py Thu Jul 10 03:47:20 2014 -0400 @@ -9,13 +9,15 @@ processes the insert without calling back into Tkinter. Although a binding to 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 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 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 commands. The function being registered provides access to the top @@ -64,9 +66,13 @@ def register(self, operation, function): '''Return OriginalCommand(operation) after registering function. - Registration adds an instance function attribute that masks the - class instance method attribute. If a second function is - registered for the same operation, the first function is replaced. + Registration adds an operation: function pair to ._operations. + It also adds an widget function attribute that masks the tkinter + 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 setattr(self.widget, operation, function) @@ -80,8 +86,10 @@ if operation in self._operations: function = self._operations[operation] del self._operations[operation] - if hasattr(self.widget, operation): + try: delattr(self.widget, operation) + except AttributeError: + pass return function else: return None @@ -160,8 +168,8 @@ if __name__ == "__main__": import unittest -## unittest.main('idlelib.idle_test.test_widgetredir', -## verbosity=2, exit=False) + unittest.main('idlelib.idle_test.test_widgetredir', + verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_widget_redirector) diff -r 220d5fdbe22e Lib/idlelib/idle_test/mock_idle.py --- a/Lib/idlelib/idle_test/mock_idle.py Thu Jul 10 01:16:49 2014 -0400 +++ b/Lib/idlelib/idle_test/mock_idle.py Thu Jul 10 03:47:20 2014 -0400 @@ -26,7 +26,10 @@ self.called = True self.args = args self.kwds = kwds - return self.result + if isinstance(self.result, BaseException): + raise self.result + else: + return self.result class Editor: diff -r 220d5fdbe22e Lib/idlelib/idle_test/test_widgetredir.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/idlelib/idle_test/test_widgetredir.py Thu Jul 10 03:47:20 2014 -0400 @@ -0,0 +1,122 @@ +"""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)