99import unittest
1010from unittest import mock
1111from idlelib .idle_test .mock_idle import Func
12- from tkinter import Tk , Frame , StringVar , IntVar , BooleanVar , DISABLED , NORMAL
12+ from tkinter import (Tk , Frame , StringVar , IntVar , BooleanVar , DISABLED ,
13+ NORMAL , TclError )
1314from idlelib import config
1415from idlelib .configdialog import idleConf , changes , tracers
1516
3132keyspage = changes ['keys' ]
3233extpage = changes ['extensions' ]
3334
35+
3436def setUpModule ():
3537 global root , dialog
3638 idleConf .userCfg = testcfg
3739 root = Tk ()
3840 # root.withdraw() # Comment out, see issue 30870
3941 dialog = configdialog .ConfigDialog (root , 'Test' , _utest = True )
4042
43+
4144def tearDownModule ():
4245 global root , dialog
4346 idleConf .userCfg = usercfg
@@ -50,6 +53,66 @@ def tearDownModule():
5053 del root
5154
5255
56+ class ConfigDialogTest (unittest .TestCase ):
57+
58+ def test_deactivate_current_config (self ):
59+ pass
60+
61+ def activate_config_changes (self ):
62+ pass
63+
64+
65+ class ConfigDialogGUITest (unittest .TestCase ):
66+
67+ def test_click_ok (self ):
68+ d = dialog
69+ apply = d .apply = mock .Mock ()
70+ destroy = d .destroy = mock .Mock ()
71+ d .buttons ['Ok' ].invoke ()
72+ apply .assert_called_once ()
73+ destroy .assert_called_once ()
74+ del d .destroy , d .apply
75+
76+ def test_click_apply (self ):
77+ d = dialog
78+ deactivate = d .deactivate_current_config = mock .Mock ()
79+ save_ext = d .save_all_changed_extensions = mock .Mock ()
80+ activate = d .activate_config_changes = mock .Mock ()
81+ d .buttons ['Apply' ].invoke ()
82+ deactivate .assert_called_once ()
83+ save_ext .assert_called_once ()
84+ activate .assert_called_once ()
85+ del d .save_all_changed_extensions
86+ del d .activate_config_changes , d .deactivate_current_config
87+
88+ def test_click_cancel (self ):
89+ d = dialog
90+ destroy = d .destroy = mock .Mock ()
91+ d .buttons ['Cancel' ].invoke ()
92+ destroy .assert_called_once ()
93+ del d .destroy
94+
95+ @mock .patch .object (configdialog , 'view_text' )
96+ def test_click_help (self , mock_textview ):
97+ d = dialog
98+
99+ # Highlight help has extra text.
100+ d .note .select (dialog .highpage )
101+ d .buttons ['Help' ].invoke ()
102+ mock_textview .assert_called_once ()
103+ kwargs = mock_textview .call_args [1 ]
104+ self .assertEqual (kwargs ['title' ], 'Help for IDLE preferences' )
105+ self .assertIn ('Highlighting:' , kwargs ['text' ])
106+
107+ # Keys help page extra text.
108+ d .note .select (dialog .keyspage )
109+ d .buttons ['Help' ].invoke ()
110+ mock_textview .assert_called ()
111+ kwargs = mock_textview .call_args [1 ]
112+ self .assertEqual (kwargs ['title' ], 'Help for IDLE preferences' )
113+ self .assertIn ('Keys:' , kwargs ['text' ])
114+
115+
53116class FontPageTest (unittest .TestCase ):
54117 """Test that font widgets enable users to make font changes.
55118
@@ -418,6 +481,90 @@ def click_it(start):
418481 eq (d .highlight_target .get (), elem [tag ])
419482 eq (d .set_highlight_target .called , count )
420483
484+ def test_highlight_sample_double_click (self ):
485+ # Test double click on highlight_sample.
486+ eq = self .assertEqual
487+ d = self .page
488+
489+ hs = d .highlight_sample
490+ hs .focus_force ()
491+ hs .see (1.0 )
492+ hs .update_idletasks ()
493+
494+ # Test binding from configdialog.
495+ hs .event_generate ('<Enter>' , x = 0 , y = 0 )
496+ hs .event_generate ('<Motion>' , x = 0 , y = 0 )
497+ # Double click is a sequence of two clicks in a row.
498+ for _ in range (2 ):
499+ hs .event_generate ('<ButtonPress-1>' , x = 0 , y = 0 )
500+ hs .event_generate ('<ButtonRelease-1>' , x = 0 , y = 0 )
501+
502+ self .assertNotEqual (d .highlight_target .get (), 'text double' )
503+ with self .assertRaises (TclError , msg = 'text doesn\' t contain any characters tagged with "sel"' ):
504+ hs .get ('sel.first' , 'sel.last' )
505+
506+ # Change binding on double click. This will allow the event to
507+ # propagate to the other levels. The default binding for double
508+ # click is to select the word that was double clicked.
509+ hs .bind ('<Double-Button-1>' , lambda e : d .highlight_target .set ('test double' ))
510+
511+ hs .event_generate ('<Enter>' , x = 0 , y = 0 )
512+ hs .event_generate ('<Motion>' , x = 0 , y = 0 )
513+ # Double click is a sequence of two clicks in a row.
514+ for _ in range (2 ):
515+ hs .event_generate ('<ButtonPress-1>' , x = 0 , y = 0 )
516+ hs .event_generate ('<ButtonRelease-1>' , x = 0 , y = 0 )
517+
518+ eq (d .highlight_target .get (), 'test double' )
519+ eq (hs .get ('sel.first' , 'sel.last' ), '\n ' )
520+
521+ # Remove selection and rebind.
522+ hs .tag_remove ('sel' , '0.0' , 'end' )
523+ hs .bind ('<Double-Button-1>' , lambda e : 'break' )
524+
525+ def test_highlight_sample_b1_motion (self ):
526+ # Test button motion on highlight_sample.
527+ eq = self .assertEqual
528+ d = self .page
529+
530+ hs = d .highlight_sample
531+ hs .focus_force ()
532+ hs .see (1.0 )
533+ hs .update_idletasks ()
534+
535+ x , y , dx , dy , offset = hs .dlineinfo ('1.0' )
536+
537+ # Test binding from configdialog.
538+ hs .event_generate ('<Leave>' )
539+ hs .event_generate ('<Enter>' )
540+ hs .event_generate ('<Motion>' , x = x , y = y )
541+ hs .event_generate ('<ButtonPress-1>' , x = x , y = y )
542+ hs .event_generate ('<B1-Motion>' , x = dx , y = dy )
543+ hs .event_generate ('<ButtonRelease-1>' , x = dx , y = dy )
544+
545+ eq (hs .tag_ranges ('sel' ), ())
546+
547+ # Remove binding on button motion. This will allow the event to
548+ # propagate to the other levels. The default binding for button
549+ # motion is to select the text that was moused over while the button
550+ # is held.
551+ hs .unbind ('<B1-Motion>' )
552+ x , y , dx , dy , offset = hs .dlineinfo ('4.0' )
553+
554+ hs .event_generate ('<Leave>' )
555+ hs .event_generate ('<Enter>' )
556+ hs .event_generate ('<Motion>' , x = x , y = y )
557+ hs .event_generate ('<ButtonPress-1>' , x = x , y = y )
558+ hs .event_generate ('<B1-Motion>' , x = x + dx , y = y + dy )
559+ hs .event_generate ('<ButtonRelease-1>' , x = x + dx , y = y + dy )
560+
561+ self .assertNotEqual (hs .tag_ranges ('sel' ), ())
562+ self .assertIn ('def' , hs .get ('sel.first' , 'sel.last' ))
563+
564+ # Remove selection and rebind.
565+ hs .tag_remove ('sel' , '0.0' , 'end' )
566+ hs .bind ('<B1-Motion>' , lambda e : 'break' )
567+
421568 def test_set_theme_type (self ):
422569 eq = self .assertEqual
423570 d = self .page
@@ -548,6 +695,17 @@ def test_create_new_and_save_new(self):
548695 self .assertFalse (d .theme_source .get ()) # Use custom set.
549696 eq (d .set_theme_type .called , 1 )
550697
698+ # Call with same name as first time to make sure it adds it only once.
699+ d .theme_source .set (True )
700+ d .builtin_name .set ('IDLE Classic' )
701+ d .create_new (first_new )
702+ eq (idleConf .GetSectionList ('user' , 'highlight' ), [first_new ])
703+ eq (idleConf .GetThemeDict ('default' , 'IDLE Classic' ),
704+ idleConf .GetThemeDict ('user' , first_new ))
705+ eq (d .custom_name .get (), first_new )
706+ self .assertFalse (d .theme_source .get ()) # Use custom set.
707+ eq (d .set_theme_type .called , 2 )
708+
551709 # Test that changed targets are in new theme.
552710 changes .add_option ('highlight' , first_new , 'hit-background' , 'yellow' )
553711 self .assertNotIn (second_new , idleConf .userCfg )
@@ -651,16 +809,21 @@ def test_delete_custom(self):
651809 idleConf .userCfg ['highlight' ].SetOption (theme_name , 'name' , 'value' )
652810 highpage [theme_name ] = {'option' : 'True' }
653811
812+ theme_name2 = 'other theme'
813+ idleConf .userCfg ['highlight' ].SetOption (theme_name2 , 'name' , 'value' )
814+ highpage [theme_name2 ] = {'option' : 'False' }
815+
654816 # Force custom theme.
655- d .theme_source .set (False )
817+ d .custom_theme_on .state (('!disabled' ,))
818+ d .custom_theme_on .invoke ()
656819 d .custom_name .set (theme_name )
657820
658821 # Cancel deletion.
659822 yesno .result = False
660823 d .button_delete_custom .invoke ()
661824 eq (yesno .called , 1 )
662825 eq (highpage [theme_name ], {'option' : 'True' })
663- eq (idleConf .GetSectionList ('user' , 'highlight' ), ['spam theme' ])
826+ eq (idleConf .GetSectionList ('user' , 'highlight' ), [theme_name , theme_name2 ])
664827 eq (dialog .deactivate_current_config .called , 0 )
665828 eq (dialog .activate_config_changes .called , 0 )
666829 eq (d .set_theme_type .called , 0 )
@@ -670,13 +833,26 @@ def test_delete_custom(self):
670833 d .button_delete_custom .invoke ()
671834 eq (yesno .called , 2 )
672835 self .assertNotIn (theme_name , highpage )
673- eq (idleConf .GetSectionList ('user' , 'highlight' ), [])
674- eq (d .custom_theme_on .state (), ('disabled' , ))
675- eq (d .custom_name .get (), '- no custom themes -' )
836+ eq (idleConf .GetSectionList ('user' , 'highlight' ), [theme_name2 ])
837+ eq (d .custom_theme_on .state (), ())
838+ eq (d .custom_name .get (), theme_name2 )
676839 eq (dialog .deactivate_current_config .called , 1 )
677840 eq (dialog .activate_config_changes .called , 1 )
678841 eq (d .set_theme_type .called , 1 )
679842
843+ # Confirm deletion of second theme - empties list.
844+ d .custom_name .set (theme_name2 )
845+ yesno .result = True
846+ d .button_delete_custom .invoke ()
847+ eq (yesno .called , 3 )
848+ self .assertNotIn (theme_name , highpage )
849+ eq (idleConf .GetSectionList ('user' , 'highlight' ), [])
850+ eq (d .custom_theme_on .state (), ('disabled' ,))
851+ eq (d .custom_name .get (), '- no custom themes -' )
852+ eq (dialog .deactivate_current_config .called , 2 )
853+ eq (dialog .activate_config_changes .called , 2 )
854+ eq (d .set_theme_type .called , 2 )
855+
680856 del dialog .activate_config_changes , dialog .deactivate_current_config
681857 del d .askyesno
682858
@@ -977,6 +1153,17 @@ def test_create_new_key_set_and_save_new_key_set(self):
9771153 self .assertFalse (d .keyset_source .get ()) # Use custom set.
9781154 eq (d .set_keys_type .called , 1 )
9791155
1156+ # Call with same name as first time to make sure it adds it only once.
1157+ d .keyset_source .set (True )
1158+ d .builtin_name .set ('IDLE Classic Windows' )
1159+ d .create_new_key_set (first_new )
1160+ eq (idleConf .GetSectionList ('user' , 'keys' ), [first_new ])
1161+ eq (idleConf .GetKeySet ('IDLE Classic Windows' ),
1162+ idleConf .GetKeySet (first_new ))
1163+ eq (d .custom_name .get (), first_new )
1164+ self .assertFalse (d .keyset_source .get ()) # Use custom set.
1165+ eq (d .set_keys_type .called , 2 )
1166+
9801167 # Test that changed keybindings are in new keyset.
9811168 changes .add_option ('keys' , first_new , 'copy' , '<Key-F11>' )
9821169 self .assertNotIn (second_new , idleConf .userCfg )
@@ -1043,16 +1230,21 @@ def test_delete_custom_keys(self):
10431230 idleConf .userCfg ['keys' ].SetOption (keyset_name , 'name' , 'value' )
10441231 keyspage [keyset_name ] = {'option' : 'True' }
10451232
1233+ keyset_name2 = 'other key set'
1234+ idleConf .userCfg ['keys' ].SetOption (keyset_name2 , 'name' , 'value' )
1235+ keyspage [keyset_name2 ] = {'option' : 'False' }
1236+
10461237 # Force custom keyset.
1047- d .keyset_source .set (False )
1238+ d .custom_keyset_on .state (('!disabled' ,))
1239+ d .custom_keyset_on .invoke ()
10481240 d .custom_name .set (keyset_name )
10491241
10501242 # Cancel deletion.
10511243 yesno .result = False
10521244 d .button_delete_custom_keys .invoke ()
10531245 eq (yesno .called , 1 )
10541246 eq (keyspage [keyset_name ], {'option' : 'True' })
1055- eq (idleConf .GetSectionList ('user' , 'keys' ), ['spam key set' ])
1247+ eq (idleConf .GetSectionList ('user' , 'keys' ), [keyset_name , keyset_name2 ])
10561248 eq (dialog .deactivate_current_config .called , 0 )
10571249 eq (dialog .activate_config_changes .called , 0 )
10581250 eq (d .set_keys_type .called , 0 )
@@ -1062,13 +1254,26 @@ def test_delete_custom_keys(self):
10621254 d .button_delete_custom_keys .invoke ()
10631255 eq (yesno .called , 2 )
10641256 self .assertNotIn (keyset_name , keyspage )
1065- eq (idleConf .GetSectionList ('user' , 'keys' ), [])
1066- eq (d .custom_keyset_on .state (), ('disabled' , ))
1067- eq (d .custom_name .get (), '- no custom keys -' )
1257+ eq (idleConf .GetSectionList ('user' , 'keys' ), [keyset_name2 ])
1258+ eq (d .custom_keyset_on .state (), ())
1259+ eq (d .custom_name .get (), keyset_name2 )
10681260 eq (dialog .deactivate_current_config .called , 1 )
10691261 eq (dialog .activate_config_changes .called , 1 )
10701262 eq (d .set_keys_type .called , 1 )
10711263
1264+ # Confirm deletion of second keyset - empties list.
1265+ d .custom_name .set (keyset_name2 )
1266+ yesno .result = True
1267+ d .button_delete_custom_keys .invoke ()
1268+ eq (yesno .called , 3 )
1269+ self .assertNotIn (keyset_name , keyspage )
1270+ eq (idleConf .GetSectionList ('user' , 'keys' ), [])
1271+ eq (d .custom_keyset_on .state (), ('disabled' ,))
1272+ eq (d .custom_name .get (), '- no custom keys -' )
1273+ eq (dialog .deactivate_current_config .called , 2 )
1274+ eq (dialog .activate_config_changes .called , 2 )
1275+ eq (d .set_keys_type .called , 2 )
1276+
10721277 del dialog .activate_config_changes , dialog .deactivate_current_config
10731278 del d .askyesno
10741279
0 commit comments