Skip to content

Commit 48151e0

Browse files
committed
Add test for menu
* Created a test for handling the menu portion of the project
1 parent 0a42d85 commit 48151e0

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

git_py_stats/tests/test_menu.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import unittest
2+
from unittest.mock import patch
3+
from io import StringIO
4+
import re
5+
6+
from git_py_stats.menu import interactive_menu
7+
8+
9+
def strip_ansi_codes(text):
10+
"""
11+
Remove ANSI escape codes from text. Necessary because, without this,
12+
capturing sys.stdout would capture the string with the raw ANSI
13+
along with it, causing our assertions to fail.
14+
"""
15+
ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
16+
return ansi_escape.sub('', text)
17+
18+
19+
class TestInteractiveMenu(unittest.TestCase):
20+
"""
21+
Unit test class for testing the interactive_menu function in menu.py.
22+
"""
23+
24+
def setUp(self):
25+
# Mock configurations for testing
26+
self.config_default = {} # Default theme
27+
self.config_legacy = {"menu_theme": "legacy"} # Legacy theme
28+
29+
@patch("builtins.input", return_value="1")
30+
@patch("sys.stdout", new_callable=StringIO)
31+
def test_default_theme_option_1(self, mock_stdout, mock_input):
32+
"""
33+
Test the interactive_menu with default theme and user selects option '1'.
34+
"""
35+
choice = interactive_menu(self.config_default)
36+
self.assertEqual(choice, "1")
37+
output = strip_ansi_codes(mock_stdout.getvalue())
38+
39+
# Check that the menu contains specific text
40+
self.assertIn("Generate:", output)
41+
self.assertIn("1) Contribution stats (by author)", output)
42+
self.assertIn("press Enter to exit", output)
43+
44+
@patch("builtins.input", return_value="22")
45+
@patch("sys.stdout", new_callable=StringIO)
46+
def test_default_theme_option_22(self, mock_stdout, mock_input):
47+
"""
48+
Test the interactive_menu with default theme and user selects option '22'.
49+
"""
50+
choice = interactive_menu(self.config_default)
51+
self.assertEqual(choice, "22")
52+
output = strip_ansi_codes(mock_stdout.getvalue())
53+
self.assertIn("Suggest:", output)
54+
self.assertIn("22) Code reviewers (based on git history)", output)
55+
56+
@patch("builtins.input", return_value="")
57+
@patch("sys.stdout", new_callable=StringIO)
58+
def test_default_theme_exit(self, mock_stdout, mock_input):
59+
"""
60+
Test the interactive_menu with default theme and user presses Enter to exit.
61+
"""
62+
choice = interactive_menu(self.config_default)
63+
self.assertEqual(choice, "")
64+
output = strip_ansi_codes(mock_stdout.getvalue())
65+
self.assertIn("press Enter to exit", output)
66+
67+
@patch("builtins.input", return_value="invalid")
68+
@patch("sys.stdout", new_callable=StringIO)
69+
def test_default_theme_invalid_input(self, mock_stdout, mock_input):
70+
"""
71+
Test the interactive_menu with default theme and user enters an invalid option.
72+
"""
73+
choice = interactive_menu(self.config_default)
74+
self.assertEqual(choice, "invalid")
75+
# Since interactive_menu doesn't print 'Invalid selection', we don't assert that here.
76+
output = strip_ansi_codes(mock_stdout.getvalue())
77+
self.assertIn("Generate:", output)
78+
79+
@patch("builtins.input", return_value="1")
80+
@patch("sys.stdout", new_callable=StringIO)
81+
def test_legacy_theme_option_1(self, mock_stdout, mock_input):
82+
"""
83+
Test the interactive_menu with legacy theme and user selects option '1'.
84+
"""
85+
choice = interactive_menu(self.config_legacy)
86+
self.assertEqual(choice, "1")
87+
output = strip_ansi_codes(mock_stdout.getvalue())
88+
self.assertIn("Generate:", output)
89+
self.assertIn("1) Contribution stats (by author)", output)
90+
91+
@patch("builtins.input", return_value="2")
92+
@patch("sys.stdout", new_callable=StringIO)
93+
def test_legacy_theme_option_2(self, mock_stdout, mock_input):
94+
"""
95+
Test the interactive_menu with legacy theme and user selects option '2'.
96+
"""
97+
choice = interactive_menu(self.config_legacy)
98+
self.assertEqual(choice, "2")
99+
output = strip_ansi_codes(mock_stdout.getvalue())
100+
self.assertIn("2) Contribution stats (by author) on a specific branch", output)
101+
102+
@patch("builtins.input", return_value="")
103+
@patch("sys.stdout", new_callable=StringIO)
104+
def test_legacy_theme_exit(self, mock_stdout, mock_input):
105+
"""
106+
Test the interactive_menu with legacy theme and user presses Enter to exit.
107+
"""
108+
choice = interactive_menu(self.config_legacy)
109+
self.assertEqual(choice, "")
110+
output = strip_ansi_codes(mock_stdout.getvalue())
111+
self.assertIn("press Enter to exit", output)
112+
113+
@patch("builtins.input", return_value="invalid")
114+
@patch("sys.stdout", new_callable=StringIO)
115+
def test_legacy_theme_invalid_input(self, mock_stdout, mock_input):
116+
"""
117+
Test the interactive_menu with legacy theme and user enters an invalid option.
118+
"""
119+
choice = interactive_menu(self.config_legacy)
120+
self.assertEqual(choice, "invalid")
121+
output = strip_ansi_codes(mock_stdout.getvalue())
122+
self.assertIn("Generate:", output)
123+
124+
@patch("builtins.input", side_effect=["1", ""])
125+
@patch("sys.stdout", new_callable=StringIO)
126+
def test_multiple_inputs(self, mock_stdout, mock_input):
127+
"""
128+
Test the interactive_menu with multiple inputs in sequence.
129+
"""
130+
choice1 = interactive_menu(self.config_default)
131+
choice2 = interactive_menu(self.config_default)
132+
self.assertEqual(choice1, "1")
133+
self.assertEqual(choice2, "")
134+
output = strip_ansi_codes(mock_stdout.getvalue())
135+
self.assertIn("Generate:", output)
136+
137+
@patch("builtins.input", return_value=" 5 ")
138+
@patch("sys.stdout", new_callable=StringIO)
139+
def test_input_with_whitespace(self, mock_stdout, mock_input):
140+
"""
141+
Test the interactive_menu with input that includes leading/trailing whitespace.
142+
"""
143+
choice = interactive_menu(self.config_default)
144+
self.assertEqual(choice, "5")
145+
output = strip_ansi_codes(mock_stdout.getvalue())
146+
self.assertIn("5) My daily status", output)
147+
148+
@patch("builtins.input", return_value="QUIT")
149+
@patch("sys.stdout", new_callable=StringIO)
150+
def test_input_quit(self, mock_stdout, mock_input):
151+
"""
152+
Test the interactive_menu with input 'QUIT' to simulate exit.
153+
"""
154+
choice = interactive_menu(self.config_default)
155+
self.assertEqual(choice, "QUIT")
156+
output = strip_ansi_codes(mock_stdout.getvalue())
157+
self.assertIn("Generate:", output)
158+
159+
@patch("builtins.input", return_value="EXIT")
160+
@patch("sys.stdout", new_callable=StringIO)
161+
def test_input_exit(self, mock_stdout, mock_input):
162+
"""
163+
Test the interactive_menu with input 'EXIT' to simulate exit.
164+
"""
165+
choice = interactive_menu(self.config_default)
166+
self.assertEqual(choice, "EXIT")
167+
output = strip_ansi_codes(mock_stdout.getvalue())
168+
self.assertIn("Generate:", output)
169+
170+
@patch("builtins.input", return_value=" ")
171+
@patch("sys.stdout", new_callable=StringIO)
172+
def test_input_only_whitespace(self, mock_stdout, mock_input):
173+
"""
174+
Test the interactive_menu with input that is only whitespace.
175+
"""
176+
choice = interactive_menu(self.config_default)
177+
self.assertEqual(choice, "")
178+
output = strip_ansi_codes(mock_stdout.getvalue())
179+
self.assertIn("press Enter to exit", output)
180+
181+
@patch("builtins.input", side_effect=KeyboardInterrupt)
182+
@patch("sys.stdout", new_callable=StringIO)
183+
def test_keyboard_interrupt(self, mock_stdout, mock_input):
184+
"""
185+
Test the interactive_menu handles KeyboardInterrupt (Ctrl+C).
186+
"""
187+
with self.assertRaises(KeyboardInterrupt):
188+
interactive_menu(self.config_default)
189+
output = strip_ansi_codes(mock_stdout.getvalue())
190+
self.assertIn("Generate:", output)
191+
192+
193+
if __name__ == "__main__":
194+
unittest.main()
195+

0 commit comments

Comments
 (0)