1 """Test tooltip, coverage 100%.
2
3 Coverage is 100% after excluding 6 lines with "# pragma: no cover".
4 They involve TclErrors that either should or should not happen in a
5 particular situation, and which are 'pass'ed if they do.
6 """
7
8 from idlelib.tooltip import TooltipBase, Hovertip
9 from test.support import requires
10 requires('gui')
11
12 from functools import wraps
13 import time
14 from tkinter import Button, Tk, Toplevel
15 import unittest
16
17
18 def setUpModule():
19 global root
20 root = Tk()
21
22 def tearDownModule():
23 global root
24 root.update_idletasks()
25 root.destroy()
26 del root
27
28
29 def 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
38 def _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
50 class ESC[4;38;5;81mToolTipBaseTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
68 class ESC[4;38;5;81mHovertipTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
160 if __name__ == '__main__':
161 unittest.main(verbosity=2)