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