1"""Test tooltip, coverage 100%. 2 3Coverage is 100% after excluding 6 lines with "# pragma: no cover". 4They involve TclErrors that either should or should not happen in a 5particular situation, and which are 'pass'ed if they do. 6""" 7 8from idlelib.tooltip import TooltipBase, Hovertip 9from test.support import requires 10requires('gui') 11 12from functools import wraps 13import time 14from tkinter import Button, Tk, Toplevel 15import unittest 16 17 18def setUpModule(): 19 global root 20 root = Tk() 21 22def tearDownModule(): 23 global root 24 root.update_idletasks() 25 root.destroy() 26 del root 27 28 29def add_call_counting(func): 30 @wraps(func) 31 def wrapped_func(*args, **kwargs): 32 wrapped_func.call_args_list.append((args, kwargs)) 33 return func(*args, **kwargs) 34 wrapped_func.call_args_list = [] 35 return wrapped_func 36 37 38def _make_top_and_button(testobj): 39 global root 40 top = Toplevel(root) 41 testobj.addCleanup(top.destroy) 42 top.title("Test tooltip") 43 button = Button(top, text='ToolTip test button') 44 button.pack() 45 testobj.addCleanup(button.destroy) 46 top.lift() 47 return top, button 48 49 50class ToolTipBaseTest(unittest.TestCase): 51 def setUp(self): 52 self.top, self.button = _make_top_and_button(self) 53 54 def test_base_class_is_unusable(self): 55 global root 56 top = Toplevel(root) 57 self.addCleanup(top.destroy) 58 59 button = Button(top, text='ToolTip test button') 60 button.pack() 61 self.addCleanup(button.destroy) 62 63 with self.assertRaises(NotImplementedError): 64 tooltip = TooltipBase(button) 65 tooltip.showtip() 66 67 68class HovertipTest(unittest.TestCase): 69 def setUp(self): 70 self.top, self.button = _make_top_and_button(self) 71 72 def is_tipwindow_shown(self, tooltip): 73 return tooltip.tipwindow and tooltip.tipwindow.winfo_viewable() 74 75 def test_showtip(self): 76 tooltip = Hovertip(self.button, 'ToolTip text') 77 self.addCleanup(tooltip.hidetip) 78 self.assertFalse(self.is_tipwindow_shown(tooltip)) 79 tooltip.showtip() 80 self.assertTrue(self.is_tipwindow_shown(tooltip)) 81 82 def test_showtip_twice(self): 83 tooltip = Hovertip(self.button, 'ToolTip text') 84 self.addCleanup(tooltip.hidetip) 85 self.assertFalse(self.is_tipwindow_shown(tooltip)) 86 tooltip.showtip() 87 self.assertTrue(self.is_tipwindow_shown(tooltip)) 88 orig_tipwindow = tooltip.tipwindow 89 tooltip.showtip() 90 self.assertTrue(self.is_tipwindow_shown(tooltip)) 91 self.assertIs(tooltip.tipwindow, orig_tipwindow) 92 93 def test_hidetip(self): 94 tooltip = Hovertip(self.button, 'ToolTip text') 95 self.addCleanup(tooltip.hidetip) 96 tooltip.showtip() 97 tooltip.hidetip() 98 self.assertFalse(self.is_tipwindow_shown(tooltip)) 99 100 def test_showtip_on_mouse_enter_no_delay(self): 101 tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None) 102 self.addCleanup(tooltip.hidetip) 103 tooltip.showtip = add_call_counting(tooltip.showtip) 104 root.update() 105 self.assertFalse(self.is_tipwindow_shown(tooltip)) 106 self.button.event_generate('<Enter>', x=0, y=0) 107 root.update() 108 self.assertTrue(self.is_tipwindow_shown(tooltip)) 109 self.assertGreater(len(tooltip.showtip.call_args_list), 0) 110 111 def test_hover_with_delay(self): 112 # Run multiple tests requiring an actual delay simultaneously. 113 114 # Test #1: A hover tip with a non-zero delay appears after the delay. 115 tooltip1 = Hovertip(self.button, 'ToolTip text', hover_delay=100) 116 self.addCleanup(tooltip1.hidetip) 117 tooltip1.showtip = add_call_counting(tooltip1.showtip) 118 root.update() 119 self.assertFalse(self.is_tipwindow_shown(tooltip1)) 120 self.button.event_generate('<Enter>', x=0, y=0) 121 root.update() 122 self.assertFalse(self.is_tipwindow_shown(tooltip1)) 123 124 # Test #2: A hover tip with a non-zero delay doesn't appear when 125 # the mouse stops hovering over the base widget before the delay 126 # expires. 127 tooltip2 = Hovertip(self.button, 'ToolTip text', hover_delay=100) 128 self.addCleanup(tooltip2.hidetip) 129 tooltip2.showtip = add_call_counting(tooltip2.showtip) 130 root.update() 131 self.button.event_generate('<Enter>', x=0, y=0) 132 root.update() 133 self.button.event_generate('<Leave>', x=0, y=0) 134 root.update() 135 136 time.sleep(0.15) 137 root.update() 138 139 # Test #1 assertions. 140 self.assertTrue(self.is_tipwindow_shown(tooltip1)) 141 self.assertGreater(len(tooltip1.showtip.call_args_list), 0) 142 143 # Test #2 assertions. 144 self.assertFalse(self.is_tipwindow_shown(tooltip2)) 145 self.assertEqual(tooltip2.showtip.call_args_list, []) 146 147 def test_hidetip_on_mouse_leave(self): 148 tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None) 149 self.addCleanup(tooltip.hidetip) 150 tooltip.showtip = add_call_counting(tooltip.showtip) 151 root.update() 152 self.button.event_generate('<Enter>', x=0, y=0) 153 root.update() 154 self.button.event_generate('<Leave>', x=0, y=0) 155 root.update() 156 self.assertFalse(self.is_tipwindow_shown(tooltip)) 157 self.assertGreater(len(tooltip.showtip.call_args_list), 0) 158 159 160if __name__ == '__main__': 161 unittest.main(verbosity=2) 162