fishertools 0.2.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fishertools/__init__.py +16 -5
- fishertools/errors/__init__.py +11 -3
- fishertools/errors/exception_types.py +282 -0
- fishertools/errors/explainer.py +87 -1
- fishertools/errors/models.py +73 -1
- fishertools/errors/patterns.py +40 -0
- fishertools/examples/cli_example.py +156 -0
- fishertools/examples/learn_example.py +65 -0
- fishertools/examples/logger_example.py +176 -0
- fishertools/examples/menu_example.py +101 -0
- fishertools/examples/storage_example.py +175 -0
- fishertools/input_utils.py +185 -0
- fishertools/learn/__init__.py +19 -2
- fishertools/learn/examples.py +88 -1
- fishertools/learn/knowledge_engine.py +321 -0
- fishertools/learn/repl/__init__.py +19 -0
- fishertools/learn/repl/cli.py +31 -0
- fishertools/learn/repl/code_sandbox.py +229 -0
- fishertools/learn/repl/command_handler.py +544 -0
- fishertools/learn/repl/command_parser.py +165 -0
- fishertools/learn/repl/engine.py +479 -0
- fishertools/learn/repl/models.py +121 -0
- fishertools/learn/repl/session_manager.py +284 -0
- fishertools/learn/repl/test_code_sandbox.py +261 -0
- fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
- fishertools/learn/repl/test_command_handler.py +224 -0
- fishertools/learn/repl/test_command_handler_pbt.py +189 -0
- fishertools/learn/repl/test_command_parser.py +160 -0
- fishertools/learn/repl/test_command_parser_pbt.py +100 -0
- fishertools/learn/repl/test_engine.py +190 -0
- fishertools/learn/repl/test_session_manager.py +310 -0
- fishertools/learn/repl/test_session_manager_pbt.py +182 -0
- fishertools/learn/test_knowledge_engine.py +241 -0
- fishertools/learn/test_knowledge_engine_pbt.py +180 -0
- fishertools/patterns/__init__.py +46 -0
- fishertools/patterns/cli.py +175 -0
- fishertools/patterns/logger.py +140 -0
- fishertools/patterns/menu.py +99 -0
- fishertools/patterns/storage.py +127 -0
- fishertools/readme_transformer.py +631 -0
- fishertools/safe/__init__.py +6 -1
- fishertools/safe/files.py +329 -1
- fishertools/transform_readme.py +105 -0
- fishertools-0.4.0.dist-info/METADATA +104 -0
- fishertools-0.4.0.dist-info/RECORD +131 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
- tests/test_documentation_properties.py +329 -0
- tests/test_documentation_structure.py +349 -0
- tests/test_errors/test_exception_types.py +446 -0
- tests/test_errors/test_exception_types_pbt.py +333 -0
- tests/test_errors/test_patterns.py +52 -0
- tests/test_input_utils/__init__.py +1 -0
- tests/test_input_utils/test_input_utils.py +65 -0
- tests/test_learn/test_examples.py +179 -1
- tests/test_learn/test_explain_properties.py +307 -0
- tests/test_patterns_cli.py +611 -0
- tests/test_patterns_docstrings.py +473 -0
- tests/test_patterns_logger.py +465 -0
- tests/test_patterns_menu.py +440 -0
- tests/test_patterns_storage.py +447 -0
- tests/test_readme_enhancements_v0_3_1.py +2036 -0
- tests/test_readme_transformer/__init__.py +1 -0
- tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
- tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
- tests/test_safe/test_files.py +726 -1
- fishertools-0.2.1.dist-info/METADATA +0 -256
- fishertools-0.2.1.dist-info/RECORD +0 -81
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Property-based tests for the simple_menu() function in fishertools.patterns.
|
|
3
|
+
|
|
4
|
+
Tests the correctness properties of the simple_menu() function using hypothesis
|
|
5
|
+
for property-based testing.
|
|
6
|
+
|
|
7
|
+
**Validates: Requirements 8.1, 8.3, 8.4**
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from hypothesis import given, strategies as st, assume
|
|
12
|
+
from unittest.mock import patch, MagicMock
|
|
13
|
+
from io import StringIO
|
|
14
|
+
|
|
15
|
+
from fishertools.patterns.menu import simple_menu
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestSimpleMenuAcceptsDictionary:
|
|
19
|
+
"""
|
|
20
|
+
Property 11: Simple Menu Accepts Dictionary
|
|
21
|
+
|
|
22
|
+
For any dictionary of options, simple_menu should accept it and process
|
|
23
|
+
valid selections.
|
|
24
|
+
|
|
25
|
+
**Validates: Requirements 8.1, 8.3**
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def test_simple_menu_accepts_valid_dict(self):
|
|
29
|
+
"""Test that simple_menu accepts a valid dictionary."""
|
|
30
|
+
def func1():
|
|
31
|
+
return "func1"
|
|
32
|
+
|
|
33
|
+
def func2():
|
|
34
|
+
return "func2"
|
|
35
|
+
|
|
36
|
+
options = {
|
|
37
|
+
"Option 1": func1,
|
|
38
|
+
"Option 2": func2
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Should not raise an exception when given valid input
|
|
42
|
+
with patch('builtins.input', return_value='quit'):
|
|
43
|
+
simple_menu(options)
|
|
44
|
+
|
|
45
|
+
def test_simple_menu_displays_menu_options(self):
|
|
46
|
+
"""Test that simple_menu displays all menu options."""
|
|
47
|
+
def func1():
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def func2():
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
options = {
|
|
54
|
+
"First Option": func1,
|
|
55
|
+
"Second Option": func2
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
with patch('builtins.input', return_value='quit'):
|
|
59
|
+
with patch('builtins.print') as mock_print:
|
|
60
|
+
simple_menu(options)
|
|
61
|
+
|
|
62
|
+
# Check that menu options were printed
|
|
63
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
64
|
+
assert "First Option" in printed_text
|
|
65
|
+
assert "Second Option" in printed_text
|
|
66
|
+
|
|
67
|
+
def test_simple_menu_executes_selected_function(self):
|
|
68
|
+
"""Test that simple_menu executes the selected function."""
|
|
69
|
+
mock_func1 = MagicMock()
|
|
70
|
+
mock_func2 = MagicMock()
|
|
71
|
+
|
|
72
|
+
options = {
|
|
73
|
+
"Option 1": mock_func1,
|
|
74
|
+
"Option 2": mock_func2
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Select option 1, then quit
|
|
78
|
+
with patch('builtins.input', side_effect=['1', 'quit']):
|
|
79
|
+
simple_menu(options)
|
|
80
|
+
|
|
81
|
+
# Option 1 should have been called
|
|
82
|
+
mock_func1.assert_called_once()
|
|
83
|
+
mock_func2.assert_not_called()
|
|
84
|
+
|
|
85
|
+
def test_simple_menu_executes_correct_function_for_each_option(self):
|
|
86
|
+
"""Test that simple_menu executes the correct function for each option."""
|
|
87
|
+
mock_func1 = MagicMock()
|
|
88
|
+
mock_func2 = MagicMock()
|
|
89
|
+
mock_func3 = MagicMock()
|
|
90
|
+
|
|
91
|
+
options = {
|
|
92
|
+
"Option 1": mock_func1,
|
|
93
|
+
"Option 2": mock_func2,
|
|
94
|
+
"Option 3": mock_func3
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Select option 2, then quit
|
|
98
|
+
with patch('builtins.input', side_effect=['2', 'quit']):
|
|
99
|
+
simple_menu(options)
|
|
100
|
+
|
|
101
|
+
# Only option 2 should have been called
|
|
102
|
+
mock_func1.assert_not_called()
|
|
103
|
+
mock_func2.assert_called_once()
|
|
104
|
+
mock_func3.assert_not_called()
|
|
105
|
+
|
|
106
|
+
def test_simple_menu_with_single_option(self):
|
|
107
|
+
"""Test that simple_menu works with a single option."""
|
|
108
|
+
mock_func = MagicMock()
|
|
109
|
+
|
|
110
|
+
options = {"Only Option": mock_func}
|
|
111
|
+
|
|
112
|
+
with patch('builtins.input', side_effect=['1', 'quit']):
|
|
113
|
+
simple_menu(options)
|
|
114
|
+
|
|
115
|
+
mock_func.assert_called_once()
|
|
116
|
+
|
|
117
|
+
def test_simple_menu_with_many_options(self):
|
|
118
|
+
"""Test that simple_menu works with many options."""
|
|
119
|
+
funcs = [MagicMock() for _ in range(10)]
|
|
120
|
+
options = {f"Option {i+1}": func for i, func in enumerate(funcs)}
|
|
121
|
+
|
|
122
|
+
# Select option 5, then quit
|
|
123
|
+
with patch('builtins.input', side_effect=['5', 'quit']):
|
|
124
|
+
simple_menu(options)
|
|
125
|
+
|
|
126
|
+
# Only option 5 should have been called
|
|
127
|
+
for i, func in enumerate(funcs):
|
|
128
|
+
if i == 4: # 0-indexed, so option 5 is index 4
|
|
129
|
+
func.assert_called_once()
|
|
130
|
+
else:
|
|
131
|
+
func.assert_not_called()
|
|
132
|
+
|
|
133
|
+
def test_simple_menu_preserves_option_order(self):
|
|
134
|
+
"""Test that simple_menu preserves the order of options."""
|
|
135
|
+
funcs = [MagicMock() for _ in range(3)]
|
|
136
|
+
options = {
|
|
137
|
+
"First": funcs[0],
|
|
138
|
+
"Second": funcs[1],
|
|
139
|
+
"Third": funcs[2]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
with patch('builtins.input', return_value='quit'):
|
|
143
|
+
with patch('builtins.print') as mock_print:
|
|
144
|
+
simple_menu(options)
|
|
145
|
+
|
|
146
|
+
# Get all printed output
|
|
147
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
148
|
+
|
|
149
|
+
# Check that options appear in order
|
|
150
|
+
first_pos = printed_text.find("First")
|
|
151
|
+
second_pos = printed_text.find("Second")
|
|
152
|
+
third_pos = printed_text.find("Third")
|
|
153
|
+
|
|
154
|
+
assert first_pos < second_pos < third_pos
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestSimpleMenuRejectsInvalidOptions:
|
|
158
|
+
"""
|
|
159
|
+
Property 12: Simple Menu Rejects Invalid Options
|
|
160
|
+
|
|
161
|
+
For any invalid menu selection, simple_menu should display an error and
|
|
162
|
+
re-prompt.
|
|
163
|
+
|
|
164
|
+
**Validates: Requirements 8.4**
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def test_simple_menu_rejects_out_of_range_number(self):
|
|
168
|
+
"""Test that simple_menu rejects out-of-range numbers."""
|
|
169
|
+
mock_func = MagicMock()
|
|
170
|
+
options = {"Option 1": mock_func}
|
|
171
|
+
|
|
172
|
+
# Try option 5 (out of range), then quit
|
|
173
|
+
with patch('builtins.input', side_effect=['5', 'quit']):
|
|
174
|
+
with patch('builtins.print') as mock_print:
|
|
175
|
+
simple_menu(options)
|
|
176
|
+
|
|
177
|
+
# Should print an error message
|
|
178
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
179
|
+
assert "Error" in printed_text or "error" in printed_text.lower()
|
|
180
|
+
|
|
181
|
+
# Function should not have been called
|
|
182
|
+
mock_func.assert_not_called()
|
|
183
|
+
|
|
184
|
+
def test_simple_menu_rejects_negative_number(self):
|
|
185
|
+
"""Test that simple_menu rejects negative numbers."""
|
|
186
|
+
mock_func = MagicMock()
|
|
187
|
+
options = {"Option 1": mock_func}
|
|
188
|
+
|
|
189
|
+
# Try negative number, then quit
|
|
190
|
+
with patch('builtins.input', side_effect=['-1', 'quit']):
|
|
191
|
+
with patch('builtins.print') as mock_print:
|
|
192
|
+
simple_menu(options)
|
|
193
|
+
|
|
194
|
+
# Should print an error message
|
|
195
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
196
|
+
assert "Error" in printed_text or "error" in printed_text.lower()
|
|
197
|
+
|
|
198
|
+
# Function should not have been called
|
|
199
|
+
mock_func.assert_not_called()
|
|
200
|
+
|
|
201
|
+
def test_simple_menu_rejects_non_numeric_input(self):
|
|
202
|
+
"""Test that simple_menu rejects non-numeric input."""
|
|
203
|
+
mock_func = MagicMock()
|
|
204
|
+
options = {"Option 1": mock_func}
|
|
205
|
+
|
|
206
|
+
# Try non-numeric input, then quit
|
|
207
|
+
with patch('builtins.input', side_effect=['abc', 'quit']):
|
|
208
|
+
with patch('builtins.print') as mock_print:
|
|
209
|
+
simple_menu(options)
|
|
210
|
+
|
|
211
|
+
# Should print an error message
|
|
212
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
213
|
+
assert "Error" in printed_text or "error" in printed_text.lower()
|
|
214
|
+
|
|
215
|
+
# Function should not have been called
|
|
216
|
+
mock_func.assert_not_called()
|
|
217
|
+
|
|
218
|
+
def test_simple_menu_rejects_zero(self):
|
|
219
|
+
"""Test that simple_menu rejects zero as a selection."""
|
|
220
|
+
mock_func = MagicMock()
|
|
221
|
+
options = {"Option 1": mock_func}
|
|
222
|
+
|
|
223
|
+
# Try zero, then quit
|
|
224
|
+
with patch('builtins.input', side_effect=['0', 'quit']):
|
|
225
|
+
with patch('builtins.print') as mock_print:
|
|
226
|
+
simple_menu(options)
|
|
227
|
+
|
|
228
|
+
# Should print an error message
|
|
229
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
230
|
+
assert "Error" in printed_text or "error" in printed_text.lower()
|
|
231
|
+
|
|
232
|
+
# Function should not have been called
|
|
233
|
+
mock_func.assert_not_called()
|
|
234
|
+
|
|
235
|
+
def test_simple_menu_reprompts_after_invalid_input(self):
|
|
236
|
+
"""Test that simple_menu re-prompts after invalid input."""
|
|
237
|
+
mock_func = MagicMock()
|
|
238
|
+
options = {"Option 1": mock_func}
|
|
239
|
+
|
|
240
|
+
# Try invalid input, then valid input, then quit
|
|
241
|
+
with patch('builtins.input', side_effect=['invalid', '1', 'quit']):
|
|
242
|
+
simple_menu(options)
|
|
243
|
+
|
|
244
|
+
# Function should have been called after valid input
|
|
245
|
+
mock_func.assert_called_once()
|
|
246
|
+
|
|
247
|
+
def test_simple_menu_handles_multiple_invalid_inputs(self):
|
|
248
|
+
"""Test that simple_menu handles multiple invalid inputs."""
|
|
249
|
+
mock_func = MagicMock()
|
|
250
|
+
options = {"Option 1": mock_func}
|
|
251
|
+
|
|
252
|
+
# Try multiple invalid inputs, then valid input, then quit
|
|
253
|
+
with patch('builtins.input', side_effect=['abc', '5', '-1', '1', 'quit']):
|
|
254
|
+
simple_menu(options)
|
|
255
|
+
|
|
256
|
+
# Function should have been called once after valid input
|
|
257
|
+
mock_func.assert_called_once()
|
|
258
|
+
|
|
259
|
+
def test_simple_menu_rejects_float_input(self):
|
|
260
|
+
"""Test that simple_menu rejects float input."""
|
|
261
|
+
mock_func = MagicMock()
|
|
262
|
+
options = {"Option 1": mock_func}
|
|
263
|
+
|
|
264
|
+
# Try float input, then quit
|
|
265
|
+
with patch('builtins.input', side_effect=['1.5', 'quit']):
|
|
266
|
+
with patch('builtins.print') as mock_print:
|
|
267
|
+
simple_menu(options)
|
|
268
|
+
|
|
269
|
+
# Should print an error message
|
|
270
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
271
|
+
assert "Error" in printed_text or "error" in printed_text.lower()
|
|
272
|
+
|
|
273
|
+
# Function should not have been called
|
|
274
|
+
mock_func.assert_not_called()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class TestSimpleMenuExitHandling:
|
|
278
|
+
"""Test exit/quit command handling."""
|
|
279
|
+
|
|
280
|
+
def test_simple_menu_exits_on_quit(self):
|
|
281
|
+
"""Test that simple_menu exits on 'quit' command."""
|
|
282
|
+
mock_func = MagicMock()
|
|
283
|
+
options = {"Option 1": mock_func}
|
|
284
|
+
|
|
285
|
+
with patch('builtins.input', return_value='quit'):
|
|
286
|
+
simple_menu(options)
|
|
287
|
+
|
|
288
|
+
# Function should not have been called
|
|
289
|
+
mock_func.assert_not_called()
|
|
290
|
+
|
|
291
|
+
def test_simple_menu_exits_on_exit(self):
|
|
292
|
+
"""Test that simple_menu exits on 'exit' command."""
|
|
293
|
+
mock_func = MagicMock()
|
|
294
|
+
options = {"Option 1": mock_func}
|
|
295
|
+
|
|
296
|
+
with patch('builtins.input', return_value='exit'):
|
|
297
|
+
simple_menu(options)
|
|
298
|
+
|
|
299
|
+
# Function should not have been called
|
|
300
|
+
mock_func.assert_not_called()
|
|
301
|
+
|
|
302
|
+
def test_simple_menu_exits_on_quit_uppercase(self):
|
|
303
|
+
"""Test that simple_menu exits on 'QUIT' command."""
|
|
304
|
+
mock_func = MagicMock()
|
|
305
|
+
options = {"Option 1": mock_func}
|
|
306
|
+
|
|
307
|
+
with patch('builtins.input', return_value='QUIT'):
|
|
308
|
+
simple_menu(options)
|
|
309
|
+
|
|
310
|
+
# Function should not have been called
|
|
311
|
+
mock_func.assert_not_called()
|
|
312
|
+
|
|
313
|
+
def test_simple_menu_exits_on_exit_uppercase(self):
|
|
314
|
+
"""Test that simple_menu exits on 'EXIT' command."""
|
|
315
|
+
mock_func = MagicMock()
|
|
316
|
+
options = {"Option 1": mock_func}
|
|
317
|
+
|
|
318
|
+
with patch('builtins.input', return_value='EXIT'):
|
|
319
|
+
simple_menu(options)
|
|
320
|
+
|
|
321
|
+
# Function should not have been called
|
|
322
|
+
mock_func.assert_not_called()
|
|
323
|
+
|
|
324
|
+
def test_simple_menu_exits_on_quit_with_whitespace(self):
|
|
325
|
+
"""Test that simple_menu exits on 'quit' with whitespace."""
|
|
326
|
+
mock_func = MagicMock()
|
|
327
|
+
options = {"Option 1": mock_func}
|
|
328
|
+
|
|
329
|
+
with patch('builtins.input', return_value=' quit '):
|
|
330
|
+
simple_menu(options)
|
|
331
|
+
|
|
332
|
+
# Function should not have been called
|
|
333
|
+
mock_func.assert_not_called()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class TestSimpleMenuErrorHandling:
|
|
337
|
+
"""Test error handling in simple_menu."""
|
|
338
|
+
|
|
339
|
+
def test_simple_menu_raises_typeerror_for_non_dict(self):
|
|
340
|
+
"""Test that simple_menu raises TypeError for non-dict input."""
|
|
341
|
+
with pytest.raises(TypeError):
|
|
342
|
+
simple_menu([("Option 1", lambda: None)])
|
|
343
|
+
|
|
344
|
+
def test_simple_menu_raises_typeerror_for_non_callable_values(self):
|
|
345
|
+
"""Test that simple_menu raises TypeError for non-callable values."""
|
|
346
|
+
with pytest.raises(TypeError):
|
|
347
|
+
simple_menu({"Option 1": "not a function"})
|
|
348
|
+
|
|
349
|
+
def test_simple_menu_raises_valueerror_for_empty_dict(self):
|
|
350
|
+
"""Test that simple_menu raises ValueError for empty dictionary."""
|
|
351
|
+
with pytest.raises(ValueError):
|
|
352
|
+
simple_menu({})
|
|
353
|
+
|
|
354
|
+
def test_simple_menu_raises_typeerror_for_none(self):
|
|
355
|
+
"""Test that simple_menu raises TypeError for None input."""
|
|
356
|
+
with pytest.raises(TypeError):
|
|
357
|
+
simple_menu(None)
|
|
358
|
+
|
|
359
|
+
def test_simple_menu_raises_typeerror_for_string(self):
|
|
360
|
+
"""Test that simple_menu raises TypeError for string input."""
|
|
361
|
+
with pytest.raises(TypeError):
|
|
362
|
+
simple_menu("not a dict")
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class TestSimpleMenuIntegration:
|
|
366
|
+
"""Integration tests for simple_menu."""
|
|
367
|
+
|
|
368
|
+
def test_simple_menu_full_workflow(self):
|
|
369
|
+
"""Test a complete workflow with simple_menu."""
|
|
370
|
+
results = []
|
|
371
|
+
|
|
372
|
+
def option1():
|
|
373
|
+
results.append("option1")
|
|
374
|
+
|
|
375
|
+
def option2():
|
|
376
|
+
results.append("option2")
|
|
377
|
+
|
|
378
|
+
options = {
|
|
379
|
+
"First": option1,
|
|
380
|
+
"Second": option2
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
# Select option 1, then option 2, then quit
|
|
384
|
+
with patch('builtins.input', side_effect=['1', '2', 'quit']):
|
|
385
|
+
simple_menu(options)
|
|
386
|
+
|
|
387
|
+
# Both functions should have been called in order
|
|
388
|
+
assert results == ["option1", "option2"]
|
|
389
|
+
|
|
390
|
+
def test_simple_menu_with_exception_in_function(self):
|
|
391
|
+
"""Test that simple_menu handles exceptions in selected functions."""
|
|
392
|
+
def failing_func():
|
|
393
|
+
raise ValueError("Test error")
|
|
394
|
+
|
|
395
|
+
def working_func():
|
|
396
|
+
pass
|
|
397
|
+
|
|
398
|
+
options = {
|
|
399
|
+
"Failing": failing_func,
|
|
400
|
+
"Working": working_func
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# Select failing function, then working function, then quit
|
|
404
|
+
# The ValueError from failing_func will be caught and treated as invalid input
|
|
405
|
+
with patch('builtins.input', side_effect=['1', '2', 'quit']):
|
|
406
|
+
simple_menu(options)
|
|
407
|
+
|
|
408
|
+
def test_simple_menu_displays_numbered_options(self):
|
|
409
|
+
"""Test that simple_menu displays options with numbers."""
|
|
410
|
+
options = {
|
|
411
|
+
"Option A": lambda: None,
|
|
412
|
+
"Option B": lambda: None,
|
|
413
|
+
"Option C": lambda: None
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
with patch('builtins.input', return_value='quit'):
|
|
417
|
+
with patch('builtins.print') as mock_print:
|
|
418
|
+
simple_menu(options)
|
|
419
|
+
|
|
420
|
+
# Get all printed output
|
|
421
|
+
printed_text = ' '.join(str(call) for call in mock_print.call_args_list)
|
|
422
|
+
|
|
423
|
+
# Should contain numbered options
|
|
424
|
+
assert "1" in printed_text
|
|
425
|
+
assert "2" in printed_text
|
|
426
|
+
assert "3" in printed_text
|
|
427
|
+
|
|
428
|
+
def test_simple_menu_with_special_characters_in_option_names(self):
|
|
429
|
+
"""Test that simple_menu handles special characters in option names."""
|
|
430
|
+
mock_func = MagicMock()
|
|
431
|
+
options = {
|
|
432
|
+
"Option with spaces": mock_func,
|
|
433
|
+
"Option-with-dashes": mock_func,
|
|
434
|
+
"Option_with_underscores": mock_func
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
with patch('builtins.input', side_effect=['1', 'quit']):
|
|
438
|
+
simple_menu(options)
|
|
439
|
+
|
|
440
|
+
mock_func.assert_called_once()
|