1import sys 2import unittest 3import tkinter 4from tkinter import ttk 5from test.support import requires, gc_collect 6from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest 7 8requires('gui') 9 10class LabeledScaleTest(AbstractTkTest, unittest.TestCase): 11 12 def tearDown(self): 13 self.root.update_idletasks() 14 super().tearDown() 15 16 def test_widget_destroy(self): 17 # automatically created variable 18 x = ttk.LabeledScale(self.root) 19 var = x._variable._name 20 x.destroy() 21 gc_collect() # For PyPy or other GCs. 22 self.assertRaises(tkinter.TclError, x.tk.globalgetvar, var) 23 24 # manually created variable 25 myvar = tkinter.DoubleVar(self.root) 26 name = myvar._name 27 x = ttk.LabeledScale(self.root, variable=myvar) 28 x.destroy() 29 if self.wantobjects: 30 self.assertEqual(x.tk.globalgetvar(name), myvar.get()) 31 else: 32 self.assertEqual(float(x.tk.globalgetvar(name)), myvar.get()) 33 del myvar 34 gc_collect() # For PyPy or other GCs. 35 self.assertRaises(tkinter.TclError, x.tk.globalgetvar, name) 36 37 # checking that the tracing callback is properly removed 38 myvar = tkinter.IntVar(self.root) 39 # LabeledScale will start tracing myvar 40 x = ttk.LabeledScale(self.root, variable=myvar) 41 x.destroy() 42 # Unless the tracing callback was removed, creating a new 43 # LabeledScale with the same var will cause an error now. This 44 # happens because the variable will be set to (possibly) a new 45 # value which causes the tracing callback to be called and then 46 # it tries calling instance attributes not yet defined. 47 ttk.LabeledScale(self.root, variable=myvar) 48 if hasattr(sys, 'last_type'): 49 self.assertNotEqual(sys.last_type, tkinter.TclError) 50 51 def test_initialization(self): 52 # master passing 53 master = tkinter.Frame(self.root) 54 x = ttk.LabeledScale(master) 55 self.assertEqual(x.master, master) 56 x.destroy() 57 58 # variable initialization/passing 59 passed_expected = (('0', 0), (0, 0), (10, 10), 60 (-1, -1), (sys.maxsize + 1, sys.maxsize + 1), 61 (2.5, 2), ('2.5', 2)) 62 for pair in passed_expected: 63 x = ttk.LabeledScale(self.root, from_=pair[0]) 64 self.assertEqual(x.value, pair[1]) 65 x.destroy() 66 x = ttk.LabeledScale(self.root, from_=None) 67 self.assertRaises((ValueError, tkinter.TclError), x._variable.get) 68 x.destroy() 69 # variable should have its default value set to the from_ value 70 myvar = tkinter.DoubleVar(self.root, value=20) 71 x = ttk.LabeledScale(self.root, variable=myvar) 72 self.assertEqual(x.value, 0) 73 x.destroy() 74 # check that it is really using a DoubleVar 75 x = ttk.LabeledScale(self.root, variable=myvar, from_=0.5) 76 self.assertEqual(x.value, 0.5) 77 self.assertEqual(x._variable._name, myvar._name) 78 x.destroy() 79 80 # widget positionment 81 def check_positions(scale, scale_pos, label, label_pos): 82 self.assertEqual(scale.pack_info()['side'], scale_pos) 83 self.assertEqual(label.place_info()['anchor'], label_pos) 84 x = ttk.LabeledScale(self.root, compound='top') 85 check_positions(x.scale, 'bottom', x.label, 'n') 86 x.destroy() 87 x = ttk.LabeledScale(self.root, compound='bottom') 88 check_positions(x.scale, 'top', x.label, 's') 89 x.destroy() 90 # invert default positions 91 x = ttk.LabeledScale(self.root, compound='unknown') 92 check_positions(x.scale, 'top', x.label, 's') 93 x.destroy() 94 x = ttk.LabeledScale(self.root) # take default positions 95 check_positions(x.scale, 'bottom', x.label, 'n') 96 x.destroy() 97 98 # extra, and invalid, kwargs 99 self.assertRaises(tkinter.TclError, ttk.LabeledScale, master, a='b') 100 101 102 def test_horizontal_range(self): 103 lscale = ttk.LabeledScale(self.root, from_=0, to=10) 104 lscale.pack() 105 lscale.update() 106 107 linfo_1 = lscale.label.place_info() 108 prev_xcoord = lscale.scale.coords()[0] 109 self.assertEqual(prev_xcoord, int(linfo_1['x'])) 110 # change range to: from -5 to 5. This should change the x coord of 111 # the scale widget, since 0 is at the middle of the new 112 # range. 113 lscale.scale.configure(from_=-5, to=5) 114 # The following update is needed since the test doesn't use mainloop, 115 # at the same time this shouldn't affect test outcome 116 lscale.update() 117 curr_xcoord = lscale.scale.coords()[0] 118 self.assertNotEqual(prev_xcoord, curr_xcoord) 119 # the label widget should have been repositioned too 120 linfo_2 = lscale.label.place_info() 121 self.assertEqual(lscale.label['text'], 0 if self.wantobjects else '0') 122 self.assertEqual(curr_xcoord, int(linfo_2['x'])) 123 # change the range back 124 lscale.scale.configure(from_=0, to=10) 125 self.assertNotEqual(prev_xcoord, curr_xcoord) 126 self.assertEqual(prev_xcoord, int(linfo_1['x'])) 127 128 lscale.destroy() 129 130 131 def test_variable_change(self): 132 x = ttk.LabeledScale(self.root) 133 x.pack() 134 x.update() 135 136 curr_xcoord = x.scale.coords()[0] 137 newval = x.value + 1 138 x.value = newval 139 # The following update is needed since the test doesn't use mainloop, 140 # at the same time this shouldn't affect test outcome 141 x.update() 142 self.assertEqual(x.value, newval) 143 self.assertEqual(x.label['text'], 144 newval if self.wantobjects else str(newval)) 145 self.assertEqual(float(x.scale.get()), newval) 146 self.assertGreater(x.scale.coords()[0], curr_xcoord) 147 self.assertEqual(x.scale.coords()[0], 148 int(x.label.place_info()['x'])) 149 150 # value outside range 151 if self.wantobjects: 152 conv = lambda x: x 153 else: 154 conv = int 155 x.value = conv(x.scale['to']) + 1 # no changes shouldn't happen 156 x.update() 157 self.assertEqual(x.value, newval) 158 self.assertEqual(conv(x.label['text']), newval) 159 self.assertEqual(float(x.scale.get()), newval) 160 self.assertEqual(x.scale.coords()[0], 161 int(x.label.place_info()['x'])) 162 163 # non-integer value 164 x.value = newval = newval + 1.5 165 x.update() 166 self.assertEqual(x.value, int(newval)) 167 self.assertEqual(conv(x.label['text']), int(newval)) 168 self.assertEqual(float(x.scale.get()), newval) 169 170 x.destroy() 171 172 173 def test_resize(self): 174 x = ttk.LabeledScale(self.root) 175 x.pack(expand=True, fill='both') 176 gc_collect() # For PyPy or other GCs. 177 x.update() 178 179 width, height = x.master.winfo_width(), x.master.winfo_height() 180 width_new, height_new = width * 2, height * 2 181 182 x.value = 3 183 x.update() 184 x.master.wm_geometry("%dx%d" % (width_new, height_new)) 185 self.assertEqual(int(x.label.place_info()['x']), 186 x.scale.coords()[0]) 187 188 # Reset geometry 189 x.master.wm_geometry("%dx%d" % (width, height)) 190 x.destroy() 191 192 193class OptionMenuTest(AbstractTkTest, unittest.TestCase): 194 195 def setUp(self): 196 super().setUp() 197 self.textvar = tkinter.StringVar(self.root) 198 199 def tearDown(self): 200 del self.textvar 201 super().tearDown() 202 203 204 def test_widget_destroy(self): 205 var = tkinter.StringVar(self.root) 206 optmenu = ttk.OptionMenu(self.root, var) 207 name = var._name 208 optmenu.update_idletasks() 209 optmenu.destroy() 210 self.assertEqual(optmenu.tk.globalgetvar(name), var.get()) 211 del var 212 gc_collect() # For PyPy or other GCs. 213 self.assertRaises(tkinter.TclError, optmenu.tk.globalgetvar, name) 214 215 216 def test_initialization(self): 217 self.assertRaises(tkinter.TclError, 218 ttk.OptionMenu, self.root, self.textvar, invalid='thing') 219 220 optmenu = ttk.OptionMenu(self.root, self.textvar, 'b', 'a', 'b') 221 self.assertEqual(optmenu._variable.get(), 'b') 222 223 self.assertTrue(optmenu['menu']) 224 self.assertTrue(optmenu['textvariable']) 225 226 optmenu.destroy() 227 228 229 def test_menu(self): 230 items = ('a', 'b', 'c') 231 default = 'a' 232 optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items) 233 found_default = False 234 for i in range(len(items)): 235 value = optmenu['menu'].entrycget(i, 'value') 236 self.assertEqual(value, items[i]) 237 if value == default: 238 found_default = True 239 self.assertTrue(found_default) 240 optmenu.destroy() 241 242 # default shouldn't be in menu if it is not part of values 243 default = 'd' 244 optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items) 245 curr = None 246 i = 0 247 while True: 248 last, curr = curr, optmenu['menu'].entryconfigure(i, 'value') 249 if last == curr: 250 # no more menu entries 251 break 252 self.assertNotEqual(curr, default) 253 i += 1 254 self.assertEqual(i, len(items)) 255 256 # check that variable is updated correctly 257 optmenu.pack() 258 gc_collect() # For PyPy or other GCs. 259 optmenu['menu'].invoke(0) 260 self.assertEqual(optmenu._variable.get(), items[0]) 261 262 # changing to an invalid index shouldn't change the variable 263 self.assertRaises(tkinter.TclError, optmenu['menu'].invoke, -1) 264 self.assertEqual(optmenu._variable.get(), items[0]) 265 266 optmenu.destroy() 267 268 # specifying a callback 269 success = [] 270 def cb_test(item): 271 self.assertEqual(item, items[1]) 272 success.append(True) 273 optmenu = ttk.OptionMenu(self.root, self.textvar, 'a', command=cb_test, 274 *items) 275 optmenu['menu'].invoke(1) 276 if not success: 277 self.fail("Menu callback not invoked") 278 279 optmenu.destroy() 280 281 def test_unique_radiobuttons(self): 282 # check that radiobuttons are unique across instances (bpo25684) 283 items = ('a', 'b', 'c') 284 default = 'a' 285 optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items) 286 textvar2 = tkinter.StringVar(self.root) 287 optmenu2 = ttk.OptionMenu(self.root, textvar2, default, *items) 288 optmenu.pack() 289 optmenu2.pack() 290 optmenu['menu'].invoke(1) 291 optmenu2['menu'].invoke(2) 292 optmenu_stringvar_name = optmenu['menu'].entrycget(0, 'variable') 293 optmenu2_stringvar_name = optmenu2['menu'].entrycget(0, 'variable') 294 self.assertNotEqual(optmenu_stringvar_name, 295 optmenu2_stringvar_name) 296 self.assertEqual(self.root.tk.globalgetvar(optmenu_stringvar_name), 297 items[1]) 298 self.assertEqual(self.root.tk.globalgetvar(optmenu2_stringvar_name), 299 items[2]) 300 301 optmenu.destroy() 302 optmenu2.destroy() 303 304 def test_trace_variable(self): 305 # prior to bpo45160, tracing a variable would cause the callback to be made twice 306 success = [] 307 items = ('a', 'b', 'c') 308 textvar = tkinter.StringVar(self.root) 309 def cb_test(*args): 310 success.append(textvar.get()) 311 optmenu = ttk.OptionMenu(self.root, textvar, "a", *items) 312 optmenu.pack() 313 cb_name = textvar.trace_add("write", cb_test) 314 optmenu['menu'].invoke(1) 315 self.assertEqual(success, ['b']) 316 self.assertEqual(textvar.get(), 'b') 317 textvar.trace_remove("write", cb_name) 318 optmenu.destroy() 319 320 321class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): 322 323 def test_labeledscale(self): 324 self._test_widget(ttk.LabeledScale) 325 326 327if __name__ == "__main__": 328 unittest.main() 329