1"""Test idlelib.ParenMatch."""
2# This must currently be a gui test because ParenMatch methods use
3# several text methods not defined on idlelib.idle_test.mock_tk.Text.
4
5import unittest
6from test.test_support import requires
7from Tkinter import Tk, Text
8from idlelib.ParenMatch import ParenMatch
9
10class Mock:  # 2.7 does not have unittest.mock
11    def __init__(self, *args, **kwargs):
12        self.called = False
13
14    def __call__(self, *args, **kwargs):
15        self.called = True
16
17    def reset_mock(self, *args, **kwargs):
18        self.called = False
19
20    def after(self, *args, **kwargs):
21        pass
22
23class DummyEditwin:
24    def __init__(self, text):
25        self.text = text
26        self.indentwidth = 8
27        self.tabwidth = 8
28        self.context_use_ps1 = True
29
30
31class ParenMatchTest(unittest.TestCase):
32
33    @classmethod
34    def setUpClass(cls):
35        requires('gui')
36        cls.root = Tk()
37        cls.text = Text(cls.root)
38        cls.editwin = DummyEditwin(cls.text)
39        cls.editwin.text_frame = Mock()
40
41    @classmethod
42    def tearDownClass(cls):
43        del cls.text, cls.editwin
44        cls.root.destroy()
45        del cls.root
46
47    def tearDown(self):
48        self.text.delete('1.0', 'end')
49
50    def test_paren_expression(self):
51        """
52        Test ParenMatch with 'expression' style.
53        """
54        text = self.text
55        pm = ParenMatch(self.editwin)
56        pm.set_style('expression')
57
58        text.insert('insert', 'def foobar(a, b')
59        pm.flash_paren_event('event')
60        self.assertIn('<<parenmatch-check-restore>>', text.event_info())
61        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
62                             ('1.10', '1.15'))
63        text.insert('insert', ')')
64        pm.restore_event()
65        self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
66        self.assertEqual(text.tag_prevrange('paren', 'end'), ())
67
68        # paren_closed_event can only be tested as below
69        pm.paren_closed_event('event')
70        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
71                                                ('1.10', '1.16'))
72
73    def test_paren_default(self):
74        """
75        Test ParenMatch with 'default' style.
76        """
77        text = self.text
78        pm = ParenMatch(self.editwin)
79        pm.set_style('default')
80
81        text.insert('insert', 'def foobar(a, b')
82        pm.flash_paren_event('event')
83        self.assertIn('<<parenmatch-check-restore>>', text.event_info())
84        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
85                             ('1.10', '1.11'))
86        text.insert('insert', ')')
87        pm.restore_event()
88        self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
89        self.assertEqual(text.tag_prevrange('paren', 'end'), ())
90
91    def test_paren_corner(self):
92        """
93        Test corner cases in flash_paren_event and paren_closed_event.
94
95        These cases force conditional expression and alternate paths.
96        """
97        text = self.text
98        pm = ParenMatch(self.editwin)
99
100        text.insert('insert', '# this is a commen)')
101        self.assertIsNone(pm.paren_closed_event('event'))
102
103        text.insert('insert', '\ndef')
104        self.assertIsNone(pm.flash_paren_event('event'))
105        self.assertIsNone(pm.paren_closed_event('event'))
106
107        text.insert('insert', ' a, *arg)')
108        self.assertIsNone(pm.paren_closed_event('event'))
109
110    def test_handle_restore_timer(self):
111        pm = ParenMatch(self.editwin)
112        pm.restore_event = Mock()
113        pm.handle_restore_timer(0)
114        self.assertTrue(pm.restore_event.called)
115        pm.restore_event.reset_mock()
116        pm.handle_restore_timer(1)
117        self.assertFalse(pm.restore_event.called)
118
119
120if __name__ == '__main__':
121    unittest.main(verbosity=2)
122