test_configdialog.py 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549
  1. """Test configdialog, coverage 94%.
  2. Half the class creates dialog, half works with user customizations.
  3. """
  4. from idlelib import configdialog
  5. from test.support import requires
  6. requires('gui')
  7. import unittest
  8. from unittest import mock
  9. from idlelib.idle_test.mock_idle import Func
  10. from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
  11. from idlelib import config
  12. from idlelib.configdialog import idleConf, changes, tracers
  13. # Tests should not depend on fortuitous user configurations.
  14. # They must not affect actual user .cfg files.
  15. # Use solution from test_config: empty parsers with no filename.
  16. usercfg = idleConf.userCfg
  17. testcfg = {
  18. 'main': config.IdleUserConfParser(''),
  19. 'highlight': config.IdleUserConfParser(''),
  20. 'keys': config.IdleUserConfParser(''),
  21. 'extensions': config.IdleUserConfParser(''),
  22. }
  23. root = None
  24. dialog = None
  25. mainpage = changes['main']
  26. highpage = changes['highlight']
  27. keyspage = changes['keys']
  28. extpage = changes['extensions']
  29. def setUpModule():
  30. global root, dialog
  31. idleConf.userCfg = testcfg
  32. root = Tk()
  33. # root.withdraw() # Comment out, see issue 30870
  34. dialog = configdialog.ConfigDialog(root, 'Test', _utest=True)
  35. def tearDownModule():
  36. global root, dialog
  37. idleConf.userCfg = usercfg
  38. tracers.detach()
  39. tracers.clear()
  40. changes.clear()
  41. root.update_idletasks()
  42. root.destroy()
  43. root = dialog = None
  44. class ConfigDialogTest(unittest.TestCase):
  45. def test_deactivate_current_config(self):
  46. pass
  47. def activate_config_changes(self):
  48. pass
  49. class ButtonTest(unittest.TestCase):
  50. def test_click_ok(self):
  51. d = dialog
  52. apply = d.apply = mock.Mock()
  53. destroy = d.destroy = mock.Mock()
  54. d.buttons['Ok'].invoke()
  55. apply.assert_called_once()
  56. destroy.assert_called_once()
  57. del d.destroy, d.apply
  58. def test_click_apply(self):
  59. d = dialog
  60. deactivate = d.deactivate_current_config = mock.Mock()
  61. save_ext = d.save_all_changed_extensions = mock.Mock()
  62. activate = d.activate_config_changes = mock.Mock()
  63. d.buttons['Apply'].invoke()
  64. deactivate.assert_called_once()
  65. save_ext.assert_called_once()
  66. activate.assert_called_once()
  67. del d.save_all_changed_extensions
  68. del d.activate_config_changes, d.deactivate_current_config
  69. def test_click_cancel(self):
  70. d = dialog
  71. d.destroy = Func()
  72. changes['main']['something'] = 1
  73. d.buttons['Cancel'].invoke()
  74. self.assertEqual(changes['main'], {})
  75. self.assertEqual(d.destroy.called, 1)
  76. del d.destroy
  77. def test_click_help(self):
  78. dialog.note.select(dialog.keyspage)
  79. with mock.patch.object(configdialog, 'view_text',
  80. new_callable=Func) as view:
  81. dialog.buttons['Help'].invoke()
  82. title, contents = view.kwds['title'], view.kwds['contents']
  83. self.assertEqual(title, 'Help for IDLE preferences')
  84. self.assertTrue(contents.startswith('When you click') and
  85. contents.endswith('a different name.\n'))
  86. class FontPageTest(unittest.TestCase):
  87. """Test that font widgets enable users to make font changes.
  88. Test that widget actions set vars, that var changes add three
  89. options to changes and call set_samples, and that set_samples
  90. changes the font of both sample boxes.
  91. """
  92. @classmethod
  93. def setUpClass(cls):
  94. page = cls.page = dialog.fontpage
  95. dialog.note.select(page)
  96. page.set_samples = Func() # Mask instance method.
  97. page.update()
  98. @classmethod
  99. def tearDownClass(cls):
  100. del cls.page.set_samples # Unmask instance method.
  101. def setUp(self):
  102. changes.clear()
  103. def test_load_font_cfg(self):
  104. # Leave widget load test to human visual check.
  105. # TODO Improve checks when add IdleConf.get_font_values.
  106. tracers.detach()
  107. d = self.page
  108. d.font_name.set('Fake')
  109. d.font_size.set('1')
  110. d.font_bold.set(True)
  111. d.set_samples.called = 0
  112. d.load_font_cfg()
  113. self.assertNotEqual(d.font_name.get(), 'Fake')
  114. self.assertNotEqual(d.font_size.get(), '1')
  115. self.assertFalse(d.font_bold.get())
  116. self.assertEqual(d.set_samples.called, 1)
  117. tracers.attach()
  118. def test_fontlist_key(self):
  119. # Up and Down keys should select a new font.
  120. d = self.page
  121. if d.fontlist.size() < 2:
  122. self.skipTest('need at least 2 fonts')
  123. fontlist = d.fontlist
  124. fontlist.activate(0)
  125. font = d.fontlist.get('active')
  126. # Test Down key.
  127. fontlist.focus_force()
  128. fontlist.update()
  129. fontlist.event_generate('<Key-Down>')
  130. fontlist.event_generate('<KeyRelease-Down>')
  131. down_font = fontlist.get('active')
  132. self.assertNotEqual(down_font, font)
  133. self.assertIn(d.font_name.get(), down_font.lower())
  134. # Test Up key.
  135. fontlist.focus_force()
  136. fontlist.update()
  137. fontlist.event_generate('<Key-Up>')
  138. fontlist.event_generate('<KeyRelease-Up>')
  139. up_font = fontlist.get('active')
  140. self.assertEqual(up_font, font)
  141. self.assertIn(d.font_name.get(), up_font.lower())
  142. def test_fontlist_mouse(self):
  143. # Click on item should select that item.
  144. d = self.page
  145. if d.fontlist.size() < 2:
  146. self.skipTest('need at least 2 fonts')
  147. fontlist = d.fontlist
  148. fontlist.activate(0)
  149. # Select next item in listbox
  150. fontlist.focus_force()
  151. fontlist.see(1)
  152. fontlist.update()
  153. x, y, dx, dy = fontlist.bbox(1)
  154. x += dx // 2
  155. y += dy // 2
  156. fontlist.event_generate('<Button-1>', x=x, y=y)
  157. fontlist.event_generate('<ButtonRelease-1>', x=x, y=y)
  158. font1 = fontlist.get(1)
  159. select_font = fontlist.get('anchor')
  160. self.assertEqual(select_font, font1)
  161. self.assertIn(d.font_name.get(), font1.lower())
  162. def test_sizelist(self):
  163. # Click on number should select that number
  164. d = self.page
  165. d.sizelist.variable.set(40)
  166. self.assertEqual(d.font_size.get(), '40')
  167. def test_bold_toggle(self):
  168. # Click on checkbutton should invert it.
  169. d = self.page
  170. d.font_bold.set(False)
  171. d.bold_toggle.invoke()
  172. self.assertTrue(d.font_bold.get())
  173. d.bold_toggle.invoke()
  174. self.assertFalse(d.font_bold.get())
  175. def test_font_set(self):
  176. # Test that setting a font Variable results in 3 provisional
  177. # change entries and a call to set_samples. Use values sure to
  178. # not be defaults.
  179. default_font = idleConf.GetFont(root, 'main', 'EditorWindow')
  180. default_size = str(default_font[1])
  181. default_bold = default_font[2] == 'bold'
  182. d = self.page
  183. d.font_size.set(default_size)
  184. d.font_bold.set(default_bold)
  185. d.set_samples.called = 0
  186. d.font_name.set('Test Font')
  187. expected = {'EditorWindow': {'font': 'Test Font',
  188. 'font-size': default_size,
  189. 'font-bold': str(default_bold)}}
  190. self.assertEqual(mainpage, expected)
  191. self.assertEqual(d.set_samples.called, 1)
  192. changes.clear()
  193. d.font_size.set('20')
  194. expected = {'EditorWindow': {'font': 'Test Font',
  195. 'font-size': '20',
  196. 'font-bold': str(default_bold)}}
  197. self.assertEqual(mainpage, expected)
  198. self.assertEqual(d.set_samples.called, 2)
  199. changes.clear()
  200. d.font_bold.set(not default_bold)
  201. expected = {'EditorWindow': {'font': 'Test Font',
  202. 'font-size': '20',
  203. 'font-bold': str(not default_bold)}}
  204. self.assertEqual(mainpage, expected)
  205. self.assertEqual(d.set_samples.called, 3)
  206. def test_set_samples(self):
  207. d = self.page
  208. del d.set_samples # Unmask method for test
  209. orig_samples = d.font_sample, d.highlight_sample
  210. d.font_sample, d.highlight_sample = {}, {}
  211. d.font_name.set('test')
  212. d.font_size.set('5')
  213. d.font_bold.set(1)
  214. expected = {'font': ('test', '5', 'bold')}
  215. # Test set_samples.
  216. d.set_samples()
  217. self.assertTrue(d.font_sample == d.highlight_sample == expected)
  218. d.font_sample, d.highlight_sample = orig_samples
  219. d.set_samples = Func() # Re-mask for other tests.
  220. class IndentTest(unittest.TestCase):
  221. @classmethod
  222. def setUpClass(cls):
  223. cls.page = dialog.fontpage
  224. cls.page.update()
  225. def test_load_tab_cfg(self):
  226. d = self.page
  227. d.space_num.set(16)
  228. d.load_tab_cfg()
  229. self.assertEqual(d.space_num.get(), 4)
  230. def test_indent_scale(self):
  231. d = self.page
  232. changes.clear()
  233. d.indent_scale.set(20)
  234. self.assertEqual(d.space_num.get(), 16)
  235. self.assertEqual(mainpage, {'Indent': {'num-spaces': '16'}})
  236. class HighPageTest(unittest.TestCase):
  237. """Test that highlight tab widgets enable users to make changes.
  238. Test that widget actions set vars, that var changes add
  239. options to changes and that themes work correctly.
  240. """
  241. @classmethod
  242. def setUpClass(cls):
  243. page = cls.page = dialog.highpage
  244. dialog.note.select(page)
  245. page.set_theme_type = Func()
  246. page.paint_theme_sample = Func()
  247. page.set_highlight_target = Func()
  248. page.set_color_sample = Func()
  249. page.update()
  250. @classmethod
  251. def tearDownClass(cls):
  252. d = cls.page
  253. del d.set_theme_type, d.paint_theme_sample
  254. del d.set_highlight_target, d.set_color_sample
  255. def setUp(self):
  256. d = self.page
  257. # The following is needed for test_load_key_cfg, _delete_custom_keys.
  258. # This may indicate a defect in some test or function.
  259. for section in idleConf.GetSectionList('user', 'highlight'):
  260. idleConf.userCfg['highlight'].remove_section(section)
  261. changes.clear()
  262. d.set_theme_type.called = 0
  263. d.paint_theme_sample.called = 0
  264. d.set_highlight_target.called = 0
  265. d.set_color_sample.called = 0
  266. def test_load_theme_cfg(self):
  267. tracers.detach()
  268. d = self.page
  269. eq = self.assertEqual
  270. # Use builtin theme with no user themes created.
  271. idleConf.CurrentTheme = mock.Mock(return_value='IDLE Classic')
  272. d.load_theme_cfg()
  273. self.assertTrue(d.theme_source.get())
  274. # builtinlist sets variable builtin_name to the CurrentTheme default.
  275. eq(d.builtin_name.get(), 'IDLE Classic')
  276. eq(d.custom_name.get(), '- no custom themes -')
  277. eq(d.custom_theme_on.state(), ('disabled',))
  278. eq(d.set_theme_type.called, 1)
  279. eq(d.paint_theme_sample.called, 1)
  280. eq(d.set_highlight_target.called, 1)
  281. # Builtin theme with non-empty user theme list.
  282. idleConf.SetOption('highlight', 'test1', 'option', 'value')
  283. idleConf.SetOption('highlight', 'test2', 'option2', 'value2')
  284. d.load_theme_cfg()
  285. eq(d.builtin_name.get(), 'IDLE Classic')
  286. eq(d.custom_name.get(), 'test1')
  287. eq(d.set_theme_type.called, 2)
  288. eq(d.paint_theme_sample.called, 2)
  289. eq(d.set_highlight_target.called, 2)
  290. # Use custom theme.
  291. idleConf.CurrentTheme = mock.Mock(return_value='test2')
  292. idleConf.SetOption('main', 'Theme', 'default', '0')
  293. d.load_theme_cfg()
  294. self.assertFalse(d.theme_source.get())
  295. eq(d.builtin_name.get(), 'IDLE Classic')
  296. eq(d.custom_name.get(), 'test2')
  297. eq(d.set_theme_type.called, 3)
  298. eq(d.paint_theme_sample.called, 3)
  299. eq(d.set_highlight_target.called, 3)
  300. del idleConf.CurrentTheme
  301. tracers.attach()
  302. def test_theme_source(self):
  303. eq = self.assertEqual
  304. d = self.page
  305. # Test these separately.
  306. d.var_changed_builtin_name = Func()
  307. d.var_changed_custom_name = Func()
  308. # Builtin selected.
  309. d.builtin_theme_on.invoke()
  310. eq(mainpage, {'Theme': {'default': 'True'}})
  311. eq(d.var_changed_builtin_name.called, 1)
  312. eq(d.var_changed_custom_name.called, 0)
  313. changes.clear()
  314. # Custom selected.
  315. d.custom_theme_on.state(('!disabled',))
  316. d.custom_theme_on.invoke()
  317. self.assertEqual(mainpage, {'Theme': {'default': 'False'}})
  318. eq(d.var_changed_builtin_name.called, 1)
  319. eq(d.var_changed_custom_name.called, 1)
  320. del d.var_changed_builtin_name, d.var_changed_custom_name
  321. def test_builtin_name(self):
  322. eq = self.assertEqual
  323. d = self.page
  324. item_list = ['IDLE Classic', 'IDLE Dark', 'IDLE New']
  325. # Not in old_themes, defaults name to first item.
  326. idleConf.SetOption('main', 'Theme', 'name', 'spam')
  327. d.builtinlist.SetMenu(item_list, 'IDLE Dark')
  328. eq(mainpage, {'Theme': {'name': 'IDLE Classic',
  329. 'name2': 'IDLE Dark'}})
  330. eq(d.theme_message['text'], 'New theme, see Help')
  331. eq(d.paint_theme_sample.called, 1)
  332. # Not in old themes - uses name2.
  333. changes.clear()
  334. idleConf.SetOption('main', 'Theme', 'name', 'IDLE New')
  335. d.builtinlist.SetMenu(item_list, 'IDLE Dark')
  336. eq(mainpage, {'Theme': {'name2': 'IDLE Dark'}})
  337. eq(d.theme_message['text'], 'New theme, see Help')
  338. eq(d.paint_theme_sample.called, 2)
  339. # Builtin name in old_themes.
  340. changes.clear()
  341. d.builtinlist.SetMenu(item_list, 'IDLE Classic')
  342. eq(mainpage, {'Theme': {'name': 'IDLE Classic', 'name2': ''}})
  343. eq(d.theme_message['text'], '')
  344. eq(d.paint_theme_sample.called, 3)
  345. def test_custom_name(self):
  346. d = self.page
  347. # If no selections, doesn't get added.
  348. d.customlist.SetMenu([], '- no custom themes -')
  349. self.assertNotIn('Theme', mainpage)
  350. self.assertEqual(d.paint_theme_sample.called, 0)
  351. # Custom name selected.
  352. changes.clear()
  353. d.customlist.SetMenu(['a', 'b', 'c'], 'c')
  354. self.assertEqual(mainpage, {'Theme': {'name': 'c'}})
  355. self.assertEqual(d.paint_theme_sample.called, 1)
  356. def test_color(self):
  357. d = self.page
  358. d.on_new_color_set = Func()
  359. # self.color is only set in get_color through ColorChooser.
  360. d.color.set('green')
  361. self.assertEqual(d.on_new_color_set.called, 1)
  362. del d.on_new_color_set
  363. def test_highlight_target_list_mouse(self):
  364. # Set highlight_target through targetlist.
  365. eq = self.assertEqual
  366. d = self.page
  367. d.targetlist.SetMenu(['a', 'b', 'c'], 'c')
  368. eq(d.highlight_target.get(), 'c')
  369. eq(d.set_highlight_target.called, 1)
  370. def test_highlight_target_text_mouse(self):
  371. # Set highlight_target through clicking highlight_sample.
  372. eq = self.assertEqual
  373. d = self.page
  374. elem = {}
  375. count = 0
  376. hs = d.highlight_sample
  377. hs.focus_force()
  378. hs.see(1.0)
  379. hs.update_idletasks()
  380. def tag_to_element(elem):
  381. for element, tag in d.theme_elements.items():
  382. elem[tag[0]] = element
  383. def click_it(start):
  384. x, y, dx, dy = hs.bbox(start)
  385. x += dx // 2
  386. y += dy // 2
  387. hs.event_generate('<Enter>', x=0, y=0)
  388. hs.event_generate('<Motion>', x=x, y=y)
  389. hs.event_generate('<ButtonPress-1>', x=x, y=y)
  390. hs.event_generate('<ButtonRelease-1>', x=x, y=y)
  391. # Flip theme_elements to make the tag the key.
  392. tag_to_element(elem)
  393. # If highlight_sample has a tag that isn't in theme_elements, there
  394. # will be a KeyError in the test run.
  395. for tag in hs.tag_names():
  396. for start_index in hs.tag_ranges(tag)[0::2]:
  397. count += 1
  398. click_it(start_index)
  399. eq(d.highlight_target.get(), elem[tag])
  400. eq(d.set_highlight_target.called, count)
  401. def test_highlight_sample_double_click(self):
  402. # Test double click on highlight_sample.
  403. eq = self.assertEqual
  404. d = self.page
  405. hs = d.highlight_sample
  406. hs.focus_force()
  407. hs.see(1.0)
  408. hs.update_idletasks()
  409. # Test binding from configdialog.
  410. hs.event_generate('<Enter>', x=0, y=0)
  411. hs.event_generate('<Motion>', x=0, y=0)
  412. # Double click is a sequence of two clicks in a row.
  413. for _ in range(2):
  414. hs.event_generate('<ButtonPress-1>', x=0, y=0)
  415. hs.event_generate('<ButtonRelease-1>', x=0, y=0)
  416. eq(hs.tag_ranges('sel'), ())
  417. def test_highlight_sample_b1_motion(self):
  418. # Test button motion on highlight_sample.
  419. eq = self.assertEqual
  420. d = self.page
  421. hs = d.highlight_sample
  422. hs.focus_force()
  423. hs.see(1.0)
  424. hs.update_idletasks()
  425. x, y, dx, dy, offset = hs.dlineinfo('1.0')
  426. # Test binding from configdialog.
  427. hs.event_generate('<Leave>')
  428. hs.event_generate('<Enter>')
  429. hs.event_generate('<Motion>', x=x, y=y)
  430. hs.event_generate('<ButtonPress-1>', x=x, y=y)
  431. hs.event_generate('<B1-Motion>', x=dx, y=dy)
  432. hs.event_generate('<ButtonRelease-1>', x=dx, y=dy)
  433. eq(hs.tag_ranges('sel'), ())
  434. def test_set_theme_type(self):
  435. eq = self.assertEqual
  436. d = self.page
  437. del d.set_theme_type
  438. # Builtin theme selected.
  439. d.theme_source.set(True)
  440. d.set_theme_type()
  441. eq(d.builtinlist['state'], NORMAL)
  442. eq(d.customlist['state'], DISABLED)
  443. eq(d.button_delete_custom.state(), ('disabled',))
  444. # Custom theme selected.
  445. d.theme_source.set(False)
  446. d.set_theme_type()
  447. eq(d.builtinlist['state'], DISABLED)
  448. eq(d.custom_theme_on.state(), ('selected',))
  449. eq(d.customlist['state'], NORMAL)
  450. eq(d.button_delete_custom.state(), ())
  451. d.set_theme_type = Func()
  452. def test_get_color(self):
  453. eq = self.assertEqual
  454. d = self.page
  455. orig_chooser = configdialog.tkColorChooser.askcolor
  456. chooser = configdialog.tkColorChooser.askcolor = Func()
  457. gntn = d.get_new_theme_name = Func()
  458. d.highlight_target.set('Editor Breakpoint')
  459. d.color.set('#ffffff')
  460. # Nothing selected.
  461. chooser.result = (None, None)
  462. d.button_set_color.invoke()
  463. eq(d.color.get(), '#ffffff')
  464. # Selection same as previous color.
  465. chooser.result = ('', d.style.lookup(d.frame_color_set['style'], 'background'))
  466. d.button_set_color.invoke()
  467. eq(d.color.get(), '#ffffff')
  468. # Select different color.
  469. chooser.result = ((222.8671875, 0.0, 0.0), '#de0000')
  470. # Default theme.
  471. d.color.set('#ffffff')
  472. d.theme_source.set(True)
  473. # No theme name selected therefore color not saved.
  474. gntn.result = ''
  475. d.button_set_color.invoke()
  476. eq(gntn.called, 1)
  477. eq(d.color.get(), '#ffffff')
  478. # Theme name selected.
  479. gntn.result = 'My New Theme'
  480. d.button_set_color.invoke()
  481. eq(d.custom_name.get(), gntn.result)
  482. eq(d.color.get(), '#de0000')
  483. # Custom theme.
  484. d.color.set('#ffffff')
  485. d.theme_source.set(False)
  486. d.button_set_color.invoke()
  487. eq(d.color.get(), '#de0000')
  488. del d.get_new_theme_name
  489. configdialog.tkColorChooser.askcolor = orig_chooser
  490. def test_on_new_color_set(self):
  491. d = self.page
  492. color = '#3f7cae'
  493. d.custom_name.set('Python')
  494. d.highlight_target.set('Selected Text')
  495. d.fg_bg_toggle.set(True)
  496. d.color.set(color)
  497. self.assertEqual(d.style.lookup(d.frame_color_set['style'], 'background'), color)
  498. self.assertEqual(d.highlight_sample.tag_cget('hilite', 'foreground'), color)
  499. self.assertEqual(highpage,
  500. {'Python': {'hilite-foreground': color}})
  501. def test_get_new_theme_name(self):
  502. orig_sectionname = configdialog.SectionName
  503. sn = configdialog.SectionName = Func(return_self=True)
  504. d = self.page
  505. sn.result = 'New Theme'
  506. self.assertEqual(d.get_new_theme_name(''), 'New Theme')
  507. configdialog.SectionName = orig_sectionname
  508. def test_save_as_new_theme(self):
  509. d = self.page
  510. gntn = d.get_new_theme_name = Func()
  511. d.theme_source.set(True)
  512. # No name entered.
  513. gntn.result = ''
  514. d.button_save_custom.invoke()
  515. self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
  516. # Name entered.
  517. gntn.result = 'my new theme'
  518. gntn.called = 0
  519. self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
  520. d.button_save_custom.invoke()
  521. self.assertIn(gntn.result, idleConf.userCfg['highlight'])
  522. del d.get_new_theme_name
  523. def test_create_new_and_save_new(self):
  524. eq = self.assertEqual
  525. d = self.page
  526. # Use default as previously active theme.
  527. d.theme_source.set(True)
  528. d.builtin_name.set('IDLE Classic')
  529. first_new = 'my new custom theme'
  530. second_new = 'my second custom theme'
  531. # No changes, so themes are an exact copy.
  532. self.assertNotIn(first_new, idleConf.userCfg)
  533. d.create_new(first_new)
  534. eq(idleConf.GetSectionList('user', 'highlight'), [first_new])
  535. eq(idleConf.GetThemeDict('default', 'IDLE Classic'),
  536. idleConf.GetThemeDict('user', first_new))
  537. eq(d.custom_name.get(), first_new)
  538. self.assertFalse(d.theme_source.get()) # Use custom set.
  539. eq(d.set_theme_type.called, 1)
  540. # Test that changed targets are in new theme.
  541. changes.add_option('highlight', first_new, 'hit-background', 'yellow')
  542. self.assertNotIn(second_new, idleConf.userCfg)
  543. d.create_new(second_new)
  544. eq(idleConf.GetSectionList('user', 'highlight'), [first_new, second_new])
  545. self.assertNotEqual(idleConf.GetThemeDict('user', first_new),
  546. idleConf.GetThemeDict('user', second_new))
  547. # Check that difference in themes was in `hit-background` from `changes`.
  548. idleConf.SetOption('highlight', first_new, 'hit-background', 'yellow')
  549. eq(idleConf.GetThemeDict('user', first_new),
  550. idleConf.GetThemeDict('user', second_new))
  551. def test_set_highlight_target(self):
  552. eq = self.assertEqual
  553. d = self.page
  554. del d.set_highlight_target
  555. # Target is cursor.
  556. d.highlight_target.set('Cursor')
  557. eq(d.fg_on.state(), ('disabled', 'selected'))
  558. eq(d.bg_on.state(), ('disabled',))
  559. self.assertTrue(d.fg_bg_toggle)
  560. eq(d.set_color_sample.called, 1)
  561. # Target is not cursor.
  562. d.highlight_target.set('Comment')
  563. eq(d.fg_on.state(), ('selected',))
  564. eq(d.bg_on.state(), ())
  565. self.assertTrue(d.fg_bg_toggle)
  566. eq(d.set_color_sample.called, 2)
  567. d.set_highlight_target = Func()
  568. def test_set_color_sample_binding(self):
  569. d = self.page
  570. scs = d.set_color_sample
  571. d.fg_on.invoke()
  572. self.assertEqual(scs.called, 1)
  573. d.bg_on.invoke()
  574. self.assertEqual(scs.called, 2)
  575. def test_set_color_sample(self):
  576. d = self.page
  577. del d.set_color_sample
  578. d.highlight_target.set('Selected Text')
  579. d.fg_bg_toggle.set(True)
  580. d.set_color_sample()
  581. self.assertEqual(
  582. d.style.lookup(d.frame_color_set['style'], 'background'),
  583. d.highlight_sample.tag_cget('hilite', 'foreground'))
  584. d.set_color_sample = Func()
  585. def test_paint_theme_sample(self):
  586. eq = self.assertEqual
  587. page = self.page
  588. del page.paint_theme_sample # Delete masking mock.
  589. hs_tag = page.highlight_sample.tag_cget
  590. gh = idleConf.GetHighlight
  591. # Create custom theme based on IDLE Dark.
  592. page.theme_source.set(True)
  593. page.builtin_name.set('IDLE Dark')
  594. theme = 'IDLE Test'
  595. page.create_new(theme)
  596. page.set_color_sample.called = 0
  597. # Base theme with nothing in `changes`.
  598. page.paint_theme_sample()
  599. new_console = {'foreground': 'blue',
  600. 'background': 'yellow',}
  601. for key, value in new_console.items():
  602. self.assertNotEqual(hs_tag('console', key), value)
  603. eq(page.set_color_sample.called, 1)
  604. # Apply changes.
  605. for key, value in new_console.items():
  606. changes.add_option('highlight', theme, 'console-'+key, value)
  607. page.paint_theme_sample()
  608. for key, value in new_console.items():
  609. eq(hs_tag('console', key), value)
  610. eq(page.set_color_sample.called, 2)
  611. page.paint_theme_sample = Func()
  612. def test_delete_custom(self):
  613. eq = self.assertEqual
  614. d = self.page
  615. d.button_delete_custom.state(('!disabled',))
  616. yesno = d.askyesno = Func()
  617. dialog.deactivate_current_config = Func()
  618. dialog.activate_config_changes = Func()
  619. theme_name = 'spam theme'
  620. idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value')
  621. highpage[theme_name] = {'option': 'True'}
  622. theme_name2 = 'other theme'
  623. idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value')
  624. highpage[theme_name2] = {'option': 'False'}
  625. # Force custom theme.
  626. d.custom_theme_on.state(('!disabled',))
  627. d.custom_theme_on.invoke()
  628. d.custom_name.set(theme_name)
  629. # Cancel deletion.
  630. yesno.result = False
  631. d.button_delete_custom.invoke()
  632. eq(yesno.called, 1)
  633. eq(highpage[theme_name], {'option': 'True'})
  634. eq(idleConf.GetSectionList('user', 'highlight'), [theme_name, theme_name2])
  635. eq(dialog.deactivate_current_config.called, 0)
  636. eq(dialog.activate_config_changes.called, 0)
  637. eq(d.set_theme_type.called, 0)
  638. # Confirm deletion.
  639. yesno.result = True
  640. d.button_delete_custom.invoke()
  641. eq(yesno.called, 2)
  642. self.assertNotIn(theme_name, highpage)
  643. eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
  644. eq(d.custom_theme_on.state(), ())
  645. eq(d.custom_name.get(), theme_name2)
  646. eq(dialog.deactivate_current_config.called, 1)
  647. eq(dialog.activate_config_changes.called, 1)
  648. eq(d.set_theme_type.called, 1)
  649. # Confirm deletion of second theme - empties list.
  650. d.custom_name.set(theme_name2)
  651. yesno.result = True
  652. d.button_delete_custom.invoke()
  653. eq(yesno.called, 3)
  654. self.assertNotIn(theme_name, highpage)
  655. eq(idleConf.GetSectionList('user', 'highlight'), [])
  656. eq(d.custom_theme_on.state(), ('disabled',))
  657. eq(d.custom_name.get(), '- no custom themes -')
  658. eq(dialog.deactivate_current_config.called, 2)
  659. eq(dialog.activate_config_changes.called, 2)
  660. eq(d.set_theme_type.called, 2)
  661. del dialog.activate_config_changes, dialog.deactivate_current_config
  662. del d.askyesno
  663. class KeysPageTest(unittest.TestCase):
  664. """Test that keys tab widgets enable users to make changes.
  665. Test that widget actions set vars, that var changes add
  666. options to changes and that key sets works correctly.
  667. """
  668. @classmethod
  669. def setUpClass(cls):
  670. page = cls.page = dialog.keyspage
  671. dialog.note.select(page)
  672. page.set_keys_type = Func()
  673. page.load_keys_list = Func()
  674. @classmethod
  675. def tearDownClass(cls):
  676. page = cls.page
  677. del page.set_keys_type, page.load_keys_list
  678. def setUp(self):
  679. d = self.page
  680. # The following is needed for test_load_key_cfg, _delete_custom_keys.
  681. # This may indicate a defect in some test or function.
  682. for section in idleConf.GetSectionList('user', 'keys'):
  683. idleConf.userCfg['keys'].remove_section(section)
  684. changes.clear()
  685. d.set_keys_type.called = 0
  686. d.load_keys_list.called = 0
  687. def test_load_key_cfg(self):
  688. tracers.detach()
  689. d = self.page
  690. eq = self.assertEqual
  691. # Use builtin keyset with no user keysets created.
  692. idleConf.CurrentKeys = mock.Mock(return_value='IDLE Classic OSX')
  693. d.load_key_cfg()
  694. self.assertTrue(d.keyset_source.get())
  695. # builtinlist sets variable builtin_name to the CurrentKeys default.
  696. eq(d.builtin_name.get(), 'IDLE Classic OSX')
  697. eq(d.custom_name.get(), '- no custom keys -')
  698. eq(d.custom_keyset_on.state(), ('disabled',))
  699. eq(d.set_keys_type.called, 1)
  700. eq(d.load_keys_list.called, 1)
  701. eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
  702. # Builtin keyset with non-empty user keyset list.
  703. idleConf.SetOption('keys', 'test1', 'option', 'value')
  704. idleConf.SetOption('keys', 'test2', 'option2', 'value2')
  705. d.load_key_cfg()
  706. eq(d.builtin_name.get(), 'IDLE Classic OSX')
  707. eq(d.custom_name.get(), 'test1')
  708. eq(d.set_keys_type.called, 2)
  709. eq(d.load_keys_list.called, 2)
  710. eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
  711. # Use custom keyset.
  712. idleConf.CurrentKeys = mock.Mock(return_value='test2')
  713. idleConf.default_keys = mock.Mock(return_value='IDLE Modern Unix')
  714. idleConf.SetOption('main', 'Keys', 'default', '0')
  715. d.load_key_cfg()
  716. self.assertFalse(d.keyset_source.get())
  717. eq(d.builtin_name.get(), 'IDLE Modern Unix')
  718. eq(d.custom_name.get(), 'test2')
  719. eq(d.set_keys_type.called, 3)
  720. eq(d.load_keys_list.called, 3)
  721. eq(d.load_keys_list.args, ('test2', ))
  722. del idleConf.CurrentKeys, idleConf.default_keys
  723. tracers.attach()
  724. def test_keyset_source(self):
  725. eq = self.assertEqual
  726. d = self.page
  727. # Test these separately.
  728. d.var_changed_builtin_name = Func()
  729. d.var_changed_custom_name = Func()
  730. # Builtin selected.
  731. d.builtin_keyset_on.invoke()
  732. eq(mainpage, {'Keys': {'default': 'True'}})
  733. eq(d.var_changed_builtin_name.called, 1)
  734. eq(d.var_changed_custom_name.called, 0)
  735. changes.clear()
  736. # Custom selected.
  737. d.custom_keyset_on.state(('!disabled',))
  738. d.custom_keyset_on.invoke()
  739. self.assertEqual(mainpage, {'Keys': {'default': 'False'}})
  740. eq(d.var_changed_builtin_name.called, 1)
  741. eq(d.var_changed_custom_name.called, 1)
  742. del d.var_changed_builtin_name, d.var_changed_custom_name
  743. def test_builtin_name(self):
  744. eq = self.assertEqual
  745. d = self.page
  746. idleConf.userCfg['main'].remove_section('Keys')
  747. item_list = ['IDLE Classic Windows', 'IDLE Classic OSX',
  748. 'IDLE Modern UNIX']
  749. # Not in old_keys, defaults name to first item.
  750. d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
  751. eq(mainpage, {'Keys': {'name': 'IDLE Classic Windows',
  752. 'name2': 'IDLE Modern UNIX'}})
  753. eq(d.keys_message['text'], 'New key set, see Help')
  754. eq(d.load_keys_list.called, 1)
  755. eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
  756. # Not in old keys - uses name2.
  757. changes.clear()
  758. idleConf.SetOption('main', 'Keys', 'name', 'IDLE Classic Unix')
  759. d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
  760. eq(mainpage, {'Keys': {'name2': 'IDLE Modern UNIX'}})
  761. eq(d.keys_message['text'], 'New key set, see Help')
  762. eq(d.load_keys_list.called, 2)
  763. eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
  764. # Builtin name in old_keys.
  765. changes.clear()
  766. d.builtinlist.SetMenu(item_list, 'IDLE Classic OSX')
  767. eq(mainpage, {'Keys': {'name': 'IDLE Classic OSX', 'name2': ''}})
  768. eq(d.keys_message['text'], '')
  769. eq(d.load_keys_list.called, 3)
  770. eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
  771. def test_custom_name(self):
  772. d = self.page
  773. # If no selections, doesn't get added.
  774. d.customlist.SetMenu([], '- no custom keys -')
  775. self.assertNotIn('Keys', mainpage)
  776. self.assertEqual(d.load_keys_list.called, 0)
  777. # Custom name selected.
  778. changes.clear()
  779. d.customlist.SetMenu(['a', 'b', 'c'], 'c')
  780. self.assertEqual(mainpage, {'Keys': {'name': 'c'}})
  781. self.assertEqual(d.load_keys_list.called, 1)
  782. def test_keybinding(self):
  783. idleConf.SetOption('extensions', 'ZzDummy', 'enable', 'True')
  784. d = self.page
  785. d.custom_name.set('my custom keys')
  786. d.bindingslist.delete(0, 'end')
  787. d.bindingslist.insert(0, 'copy')
  788. d.bindingslist.insert(1, 'z-in')
  789. d.bindingslist.selection_set(0)
  790. d.bindingslist.selection_anchor(0)
  791. # Core binding - adds to keys.
  792. d.keybinding.set('<Key-F11>')
  793. self.assertEqual(keyspage,
  794. {'my custom keys': {'copy': '<Key-F11>'}})
  795. # Not a core binding - adds to extensions.
  796. d.bindingslist.selection_set(1)
  797. d.bindingslist.selection_anchor(1)
  798. d.keybinding.set('<Key-F11>')
  799. self.assertEqual(extpage,
  800. {'ZzDummy_cfgBindings': {'z-in': '<Key-F11>'}})
  801. def test_set_keys_type(self):
  802. eq = self.assertEqual
  803. d = self.page
  804. del d.set_keys_type
  805. # Builtin keyset selected.
  806. d.keyset_source.set(True)
  807. d.set_keys_type()
  808. eq(d.builtinlist['state'], NORMAL)
  809. eq(d.customlist['state'], DISABLED)
  810. eq(d.button_delete_custom_keys.state(), ('disabled',))
  811. # Custom keyset selected.
  812. d.keyset_source.set(False)
  813. d.set_keys_type()
  814. eq(d.builtinlist['state'], DISABLED)
  815. eq(d.custom_keyset_on.state(), ('selected',))
  816. eq(d.customlist['state'], NORMAL)
  817. eq(d.button_delete_custom_keys.state(), ())
  818. d.set_keys_type = Func()
  819. def test_get_new_keys(self):
  820. eq = self.assertEqual
  821. d = self.page
  822. orig_getkeysdialog = configdialog.GetKeysDialog
  823. gkd = configdialog.GetKeysDialog = Func(return_self=True)
  824. gnkn = d.get_new_keys_name = Func()
  825. d.button_new_keys.state(('!disabled',))
  826. d.bindingslist.delete(0, 'end')
  827. d.bindingslist.insert(0, 'copy - <Control-Shift-Key-C>')
  828. d.bindingslist.selection_set(0)
  829. d.bindingslist.selection_anchor(0)
  830. d.keybinding.set('Key-a')
  831. d.keyset_source.set(True) # Default keyset.
  832. # Default keyset; no change to binding.
  833. gkd.result = ''
  834. d.button_new_keys.invoke()
  835. eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
  836. # Keybinding isn't changed when there isn't a change entered.
  837. eq(d.keybinding.get(), 'Key-a')
  838. # Default keyset; binding changed.
  839. gkd.result = '<Key-F11>'
  840. # No keyset name selected therefore binding not saved.
  841. gnkn.result = ''
  842. d.button_new_keys.invoke()
  843. eq(gnkn.called, 1)
  844. eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
  845. # Keyset name selected.
  846. gnkn.result = 'My New Key Set'
  847. d.button_new_keys.invoke()
  848. eq(d.custom_name.get(), gnkn.result)
  849. eq(d.bindingslist.get('anchor'), 'copy - <Key-F11>')
  850. eq(d.keybinding.get(), '<Key-F11>')
  851. # User keyset; binding changed.
  852. d.keyset_source.set(False) # Custom keyset.
  853. gnkn.called = 0
  854. gkd.result = '<Key-p>'
  855. d.button_new_keys.invoke()
  856. eq(gnkn.called, 0)
  857. eq(d.bindingslist.get('anchor'), 'copy - <Key-p>')
  858. eq(d.keybinding.get(), '<Key-p>')
  859. del d.get_new_keys_name
  860. configdialog.GetKeysDialog = orig_getkeysdialog
  861. def test_get_new_keys_name(self):
  862. orig_sectionname = configdialog.SectionName
  863. sn = configdialog.SectionName = Func(return_self=True)
  864. d = self.page
  865. sn.result = 'New Keys'
  866. self.assertEqual(d.get_new_keys_name(''), 'New Keys')
  867. configdialog.SectionName = orig_sectionname
  868. def test_save_as_new_key_set(self):
  869. d = self.page
  870. gnkn = d.get_new_keys_name = Func()
  871. d.keyset_source.set(True)
  872. # No name entered.
  873. gnkn.result = ''
  874. d.button_save_custom_keys.invoke()
  875. # Name entered.
  876. gnkn.result = 'my new key set'
  877. gnkn.called = 0
  878. self.assertNotIn(gnkn.result, idleConf.userCfg['keys'])
  879. d.button_save_custom_keys.invoke()
  880. self.assertIn(gnkn.result, idleConf.userCfg['keys'])
  881. del d.get_new_keys_name
  882. def test_on_bindingslist_select(self):
  883. d = self.page
  884. b = d.bindingslist
  885. b.delete(0, 'end')
  886. b.insert(0, 'copy')
  887. b.insert(1, 'find')
  888. b.activate(0)
  889. b.focus_force()
  890. b.see(1)
  891. b.update()
  892. x, y, dx, dy = b.bbox(1)
  893. x += dx // 2
  894. y += dy // 2
  895. b.event_generate('<Enter>', x=0, y=0)
  896. b.event_generate('<Motion>', x=x, y=y)
  897. b.event_generate('<Button-1>', x=x, y=y)
  898. b.event_generate('<ButtonRelease-1>', x=x, y=y)
  899. self.assertEqual(b.get('anchor'), 'find')
  900. self.assertEqual(d.button_new_keys.state(), ())
  901. def test_create_new_key_set_and_save_new_key_set(self):
  902. eq = self.assertEqual
  903. d = self.page
  904. # Use default as previously active keyset.
  905. d.keyset_source.set(True)
  906. d.builtin_name.set('IDLE Classic Windows')
  907. first_new = 'my new custom key set'
  908. second_new = 'my second custom keyset'
  909. # No changes, so keysets are an exact copy.
  910. self.assertNotIn(first_new, idleConf.userCfg)
  911. d.create_new_key_set(first_new)
  912. eq(idleConf.GetSectionList('user', 'keys'), [first_new])
  913. eq(idleConf.GetKeySet('IDLE Classic Windows'),
  914. idleConf.GetKeySet(first_new))
  915. eq(d.custom_name.get(), first_new)
  916. self.assertFalse(d.keyset_source.get()) # Use custom set.
  917. eq(d.set_keys_type.called, 1)
  918. # Test that changed keybindings are in new keyset.
  919. changes.add_option('keys', first_new, 'copy', '<Key-F11>')
  920. self.assertNotIn(second_new, idleConf.userCfg)
  921. d.create_new_key_set(second_new)
  922. eq(idleConf.GetSectionList('user', 'keys'), [first_new, second_new])
  923. self.assertNotEqual(idleConf.GetKeySet(first_new),
  924. idleConf.GetKeySet(second_new))
  925. # Check that difference in keysets was in option `copy` from `changes`.
  926. idleConf.SetOption('keys', first_new, 'copy', '<Key-F11>')
  927. eq(idleConf.GetKeySet(first_new), idleConf.GetKeySet(second_new))
  928. def test_load_keys_list(self):
  929. eq = self.assertEqual
  930. d = self.page
  931. gks = idleConf.GetKeySet = Func()
  932. del d.load_keys_list
  933. b = d.bindingslist
  934. b.delete(0, 'end')
  935. b.insert(0, '<<find>>')
  936. b.insert(1, '<<help>>')
  937. gks.result = {'<<copy>>': ['<Control-Key-c>', '<Control-Key-C>'],
  938. '<<force-open-completions>>': ['<Control-Key-space>'],
  939. '<<spam>>': ['<Key-F11>']}
  940. changes.add_option('keys', 'my keys', 'spam', '<Shift-Key-a>')
  941. expected = ('copy - <Control-Key-c> <Control-Key-C>',
  942. 'force-open-completions - <Control-Key-space>',
  943. 'spam - <Shift-Key-a>')
  944. # No current selection.
  945. d.load_keys_list('my keys')
  946. eq(b.get(0, 'end'), expected)
  947. eq(b.get('anchor'), '')
  948. eq(b.curselection(), ())
  949. # Check selection.
  950. b.selection_set(1)
  951. b.selection_anchor(1)
  952. d.load_keys_list('my keys')
  953. eq(b.get(0, 'end'), expected)
  954. eq(b.get('anchor'), 'force-open-completions - <Control-Key-space>')
  955. eq(b.curselection(), (1, ))
  956. # Change selection.
  957. b.selection_set(2)
  958. b.selection_anchor(2)
  959. d.load_keys_list('my keys')
  960. eq(b.get(0, 'end'), expected)
  961. eq(b.get('anchor'), 'spam - <Shift-Key-a>')
  962. eq(b.curselection(), (2, ))
  963. d.load_keys_list = Func()
  964. del idleConf.GetKeySet
  965. def test_delete_custom_keys(self):
  966. eq = self.assertEqual
  967. d = self.page
  968. d.button_delete_custom_keys.state(('!disabled',))
  969. yesno = d.askyesno = Func()
  970. dialog.deactivate_current_config = Func()
  971. dialog.activate_config_changes = Func()
  972. keyset_name = 'spam key set'
  973. idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
  974. keyspage[keyset_name] = {'option': 'True'}
  975. keyset_name2 = 'other key set'
  976. idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value')
  977. keyspage[keyset_name2] = {'option': 'False'}
  978. # Force custom keyset.
  979. d.custom_keyset_on.state(('!disabled',))
  980. d.custom_keyset_on.invoke()
  981. d.custom_name.set(keyset_name)
  982. # Cancel deletion.
  983. yesno.result = False
  984. d.button_delete_custom_keys.invoke()
  985. eq(yesno.called, 1)
  986. eq(keyspage[keyset_name], {'option': 'True'})
  987. eq(idleConf.GetSectionList('user', 'keys'), [keyset_name, keyset_name2])
  988. eq(dialog.deactivate_current_config.called, 0)
  989. eq(dialog.activate_config_changes.called, 0)
  990. eq(d.set_keys_type.called, 0)
  991. # Confirm deletion.
  992. yesno.result = True
  993. d.button_delete_custom_keys.invoke()
  994. eq(yesno.called, 2)
  995. self.assertNotIn(keyset_name, keyspage)
  996. eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
  997. eq(d.custom_keyset_on.state(), ())
  998. eq(d.custom_name.get(), keyset_name2)
  999. eq(dialog.deactivate_current_config.called, 1)
  1000. eq(dialog.activate_config_changes.called, 1)
  1001. eq(d.set_keys_type.called, 1)
  1002. # Confirm deletion of second keyset - empties list.
  1003. d.custom_name.set(keyset_name2)
  1004. yesno.result = True
  1005. d.button_delete_custom_keys.invoke()
  1006. eq(yesno.called, 3)
  1007. self.assertNotIn(keyset_name, keyspage)
  1008. eq(idleConf.GetSectionList('user', 'keys'), [])
  1009. eq(d.custom_keyset_on.state(), ('disabled',))
  1010. eq(d.custom_name.get(), '- no custom keys -')
  1011. eq(dialog.deactivate_current_config.called, 2)
  1012. eq(dialog.activate_config_changes.called, 2)
  1013. eq(d.set_keys_type.called, 2)
  1014. del dialog.activate_config_changes, dialog.deactivate_current_config
  1015. del d.askyesno
  1016. class GenPageTest(unittest.TestCase):
  1017. """Test that general tab widgets enable users to make changes.
  1018. Test that widget actions set vars, that var changes add
  1019. options to changes and that helplist works correctly.
  1020. """
  1021. @classmethod
  1022. def setUpClass(cls):
  1023. page = cls.page = dialog.genpage
  1024. dialog.note.select(page)
  1025. page.set = page.set_add_delete_state = Func()
  1026. page.upc = page.update_help_changes = Func()
  1027. page.update()
  1028. @classmethod
  1029. def tearDownClass(cls):
  1030. page = cls.page
  1031. del page.set, page.set_add_delete_state
  1032. del page.upc, page.update_help_changes
  1033. page.helplist.delete(0, 'end')
  1034. page.user_helplist.clear()
  1035. def setUp(self):
  1036. changes.clear()
  1037. def test_load_general_cfg(self):
  1038. # Set to wrong values, load, check right values.
  1039. eq = self.assertEqual
  1040. d = self.page
  1041. d.startup_edit.set(1)
  1042. d.autosave.set(1)
  1043. d.win_width.set(1)
  1044. d.win_height.set(1)
  1045. d.helplist.insert('end', 'bad')
  1046. d.user_helplist = ['bad', 'worse']
  1047. idleConf.SetOption('main', 'HelpFiles', '1', 'name;file')
  1048. d.load_general_cfg()
  1049. eq(d.startup_edit.get(), 0)
  1050. eq(d.autosave.get(), 0)
  1051. eq(d.win_width.get(), '80')
  1052. eq(d.win_height.get(), '40')
  1053. eq(d.helplist.get(0, 'end'), ('name',))
  1054. eq(d.user_helplist, [('name', 'file', '1')])
  1055. def test_startup(self):
  1056. d = self.page
  1057. d.startup_editor_on.invoke()
  1058. self.assertEqual(mainpage,
  1059. {'General': {'editor-on-startup': '1'}})
  1060. changes.clear()
  1061. d.startup_shell_on.invoke()
  1062. self.assertEqual(mainpage,
  1063. {'General': {'editor-on-startup': '0'}})
  1064. def test_editor_size(self):
  1065. d = self.page
  1066. d.win_height_int.delete(0, 'end')
  1067. d.win_height_int.insert(0, '11')
  1068. self.assertEqual(mainpage, {'EditorWindow': {'height': '11'}})
  1069. changes.clear()
  1070. d.win_width_int.delete(0, 'end')
  1071. d.win_width_int.insert(0, '11')
  1072. self.assertEqual(mainpage, {'EditorWindow': {'width': '11'}})
  1073. def test_cursor_blink(self):
  1074. self.page.cursor_blink_bool.invoke()
  1075. self.assertEqual(mainpage, {'EditorWindow': {'cursor-blink': 'False'}})
  1076. def test_autocomplete_wait(self):
  1077. self.page.auto_wait_int.delete(0, 'end')
  1078. self.page.auto_wait_int.insert(0, '11')
  1079. self.assertEqual(extpage, {'AutoComplete': {'popupwait': '11'}})
  1080. def test_parenmatch(self):
  1081. d = self.page
  1082. eq = self.assertEqual
  1083. d.paren_style_type['menu'].invoke(0)
  1084. eq(extpage, {'ParenMatch': {'style': 'opener'}})
  1085. changes.clear()
  1086. d.paren_flash_time.delete(0, 'end')
  1087. d.paren_flash_time.insert(0, '11')
  1088. eq(extpage, {'ParenMatch': {'flash-delay': '11'}})
  1089. changes.clear()
  1090. d.bell_on.invoke()
  1091. eq(extpage, {'ParenMatch': {'bell': 'False'}})
  1092. def test_autosave(self):
  1093. d = self.page
  1094. d.save_auto_on.invoke()
  1095. self.assertEqual(mainpage, {'General': {'autosave': '1'}})
  1096. d.save_ask_on.invoke()
  1097. self.assertEqual(mainpage, {'General': {'autosave': '0'}})
  1098. def test_paragraph(self):
  1099. self.page.format_width_int.delete(0, 'end')
  1100. self.page.format_width_int.insert(0, '11')
  1101. self.assertEqual(extpage, {'FormatParagraph': {'max-width': '11'}})
  1102. def test_context(self):
  1103. self.page.context_int.delete(0, 'end')
  1104. self.page.context_int.insert(0, '1')
  1105. self.assertEqual(extpage, {'CodeContext': {'maxlines': '1'}})
  1106. def test_source_selected(self):
  1107. d = self.page
  1108. d.set = d.set_add_delete_state
  1109. d.upc = d.update_help_changes
  1110. helplist = d.helplist
  1111. dex = 'end'
  1112. helplist.insert(dex, 'source')
  1113. helplist.activate(dex)
  1114. helplist.focus_force()
  1115. helplist.see(dex)
  1116. helplist.update()
  1117. x, y, dx, dy = helplist.bbox(dex)
  1118. x += dx // 2
  1119. y += dy // 2
  1120. d.set.called = d.upc.called = 0
  1121. helplist.event_generate('<Enter>', x=0, y=0)
  1122. helplist.event_generate('<Motion>', x=x, y=y)
  1123. helplist.event_generate('<Button-1>', x=x, y=y)
  1124. helplist.event_generate('<ButtonRelease-1>', x=x, y=y)
  1125. self.assertEqual(helplist.get('anchor'), 'source')
  1126. self.assertTrue(d.set.called)
  1127. self.assertFalse(d.upc.called)
  1128. def test_set_add_delete_state(self):
  1129. # Call with 0 items, 1 unselected item, 1 selected item.
  1130. eq = self.assertEqual
  1131. d = self.page
  1132. del d.set_add_delete_state # Unmask method.
  1133. sad = d.set_add_delete_state
  1134. h = d.helplist
  1135. h.delete(0, 'end')
  1136. sad()
  1137. eq(d.button_helplist_edit.state(), ('disabled',))
  1138. eq(d.button_helplist_remove.state(), ('disabled',))
  1139. h.insert(0, 'source')
  1140. sad()
  1141. eq(d.button_helplist_edit.state(), ('disabled',))
  1142. eq(d.button_helplist_remove.state(), ('disabled',))
  1143. h.selection_set(0)
  1144. sad()
  1145. eq(d.button_helplist_edit.state(), ())
  1146. eq(d.button_helplist_remove.state(), ())
  1147. d.set_add_delete_state = Func() # Mask method.
  1148. def test_helplist_item_add(self):
  1149. # Call without and twice with HelpSource result.
  1150. # Double call enables check on order.
  1151. eq = self.assertEqual
  1152. orig_helpsource = configdialog.HelpSource
  1153. hs = configdialog.HelpSource = Func(return_self=True)
  1154. d = self.page
  1155. d.helplist.delete(0, 'end')
  1156. d.user_helplist.clear()
  1157. d.set.called = d.upc.called = 0
  1158. hs.result = ''
  1159. d.helplist_item_add()
  1160. self.assertTrue(list(d.helplist.get(0, 'end')) ==
  1161. d.user_helplist == [])
  1162. self.assertFalse(d.upc.called)
  1163. hs.result = ('name1', 'file1')
  1164. d.helplist_item_add()
  1165. hs.result = ('name2', 'file2')
  1166. d.helplist_item_add()
  1167. eq(d.helplist.get(0, 'end'), ('name1', 'name2'))
  1168. eq(d.user_helplist, [('name1', 'file1'), ('name2', 'file2')])
  1169. eq(d.upc.called, 2)
  1170. self.assertFalse(d.set.called)
  1171. configdialog.HelpSource = orig_helpsource
  1172. def test_helplist_item_edit(self):
  1173. # Call without and with HelpSource change.
  1174. eq = self.assertEqual
  1175. orig_helpsource = configdialog.HelpSource
  1176. hs = configdialog.HelpSource = Func(return_self=True)
  1177. d = self.page
  1178. d.helplist.delete(0, 'end')
  1179. d.helplist.insert(0, 'name1')
  1180. d.helplist.selection_set(0)
  1181. d.helplist.selection_anchor(0)
  1182. d.user_helplist.clear()
  1183. d.user_helplist.append(('name1', 'file1'))
  1184. d.set.called = d.upc.called = 0
  1185. hs.result = ''
  1186. d.helplist_item_edit()
  1187. hs.result = ('name1', 'file1')
  1188. d.helplist_item_edit()
  1189. eq(d.helplist.get(0, 'end'), ('name1',))
  1190. eq(d.user_helplist, [('name1', 'file1')])
  1191. self.assertFalse(d.upc.called)
  1192. hs.result = ('name2', 'file2')
  1193. d.helplist_item_edit()
  1194. eq(d.helplist.get(0, 'end'), ('name2',))
  1195. eq(d.user_helplist, [('name2', 'file2')])
  1196. self.assertTrue(d.upc.called == d.set.called == 1)
  1197. configdialog.HelpSource = orig_helpsource
  1198. def test_helplist_item_remove(self):
  1199. eq = self.assertEqual
  1200. d = self.page
  1201. d.helplist.delete(0, 'end')
  1202. d.helplist.insert(0, 'name1')
  1203. d.helplist.selection_set(0)
  1204. d.helplist.selection_anchor(0)
  1205. d.user_helplist.clear()
  1206. d.user_helplist.append(('name1', 'file1'))
  1207. d.set.called = d.upc.called = 0
  1208. d.helplist_item_remove()
  1209. eq(d.helplist.get(0, 'end'), ())
  1210. eq(d.user_helplist, [])
  1211. self.assertTrue(d.upc.called == d.set.called == 1)
  1212. def test_update_help_changes(self):
  1213. d = self.page
  1214. del d.update_help_changes
  1215. d.user_helplist.clear()
  1216. d.user_helplist.append(('name1', 'file1'))
  1217. d.user_helplist.append(('name2', 'file2'))
  1218. d.update_help_changes()
  1219. self.assertEqual(mainpage['HelpFiles'],
  1220. {'1': 'name1;file1', '2': 'name2;file2'})
  1221. d.update_help_changes = Func()
  1222. class VarTraceTest(unittest.TestCase):
  1223. @classmethod
  1224. def setUpClass(cls):
  1225. cls.tracers = configdialog.VarTrace()
  1226. cls.iv = IntVar(root)
  1227. cls.bv = BooleanVar(root)
  1228. @classmethod
  1229. def tearDownClass(cls):
  1230. del cls.tracers, cls.iv, cls.bv
  1231. def setUp(self):
  1232. self.tracers.clear()
  1233. self.called = 0
  1234. def var_changed_increment(self, *params):
  1235. self.called += 13
  1236. def var_changed_boolean(self, *params):
  1237. pass
  1238. def test_init(self):
  1239. tr = self.tracers
  1240. tr.__init__()
  1241. self.assertEqual(tr.untraced, [])
  1242. self.assertEqual(tr.traced, [])
  1243. def test_clear(self):
  1244. tr = self.tracers
  1245. tr.untraced.append(0)
  1246. tr.traced.append(1)
  1247. tr.clear()
  1248. self.assertEqual(tr.untraced, [])
  1249. self.assertEqual(tr.traced, [])
  1250. def test_add(self):
  1251. tr = self.tracers
  1252. func = Func()
  1253. cb = tr.make_callback = mock.Mock(return_value=func)
  1254. iv = tr.add(self.iv, self.var_changed_increment)
  1255. self.assertIs(iv, self.iv)
  1256. bv = tr.add(self.bv, self.var_changed_boolean)
  1257. self.assertIs(bv, self.bv)
  1258. sv = StringVar(root)
  1259. sv2 = tr.add(sv, ('main', 'section', 'option'))
  1260. self.assertIs(sv2, sv)
  1261. cb.assert_called_once()
  1262. cb.assert_called_with(sv, ('main', 'section', 'option'))
  1263. expected = [(iv, self.var_changed_increment),
  1264. (bv, self.var_changed_boolean),
  1265. (sv, func)]
  1266. self.assertEqual(tr.traced, [])
  1267. self.assertEqual(tr.untraced, expected)
  1268. del tr.make_callback
  1269. def test_make_callback(self):
  1270. cb = self.tracers.make_callback(self.iv, ('main', 'section', 'option'))
  1271. self.assertTrue(callable(cb))
  1272. self.iv.set(42)
  1273. # Not attached, so set didn't invoke the callback.
  1274. self.assertNotIn('section', changes['main'])
  1275. # Invoke callback manually.
  1276. cb()
  1277. self.assertIn('section', changes['main'])
  1278. self.assertEqual(changes['main']['section']['option'], '42')
  1279. changes.clear()
  1280. def test_attach_detach(self):
  1281. tr = self.tracers
  1282. iv = tr.add(self.iv, self.var_changed_increment)
  1283. bv = tr.add(self.bv, self.var_changed_boolean)
  1284. expected = [(iv, self.var_changed_increment),
  1285. (bv, self.var_changed_boolean)]
  1286. # Attach callbacks and test call increment.
  1287. tr.attach()
  1288. self.assertEqual(tr.untraced, [])
  1289. self.assertCountEqual(tr.traced, expected)
  1290. iv.set(1)
  1291. self.assertEqual(iv.get(), 1)
  1292. self.assertEqual(self.called, 13)
  1293. # Check that only one callback is attached to a variable.
  1294. # If more than one callback were attached, then var_changed_increment
  1295. # would be called twice and the counter would be 2.
  1296. self.called = 0
  1297. tr.attach()
  1298. iv.set(1)
  1299. self.assertEqual(self.called, 13)
  1300. # Detach callbacks.
  1301. self.called = 0
  1302. tr.detach()
  1303. self.assertEqual(tr.traced, [])
  1304. self.assertCountEqual(tr.untraced, expected)
  1305. iv.set(1)
  1306. self.assertEqual(self.called, 0)
  1307. if __name__ == '__main__':
  1308. unittest.main(verbosity=2)