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,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the CommandParser class.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from fishertools.learn.repl.command_parser import CommandParser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestCommandParserBasic:
|
|
10
|
+
"""Test basic command parsing functionality."""
|
|
11
|
+
|
|
12
|
+
def test_parse_simple_command(self):
|
|
13
|
+
"""Test parsing a simple command without arguments."""
|
|
14
|
+
cmd_type, args = CommandParser.parse("/help")
|
|
15
|
+
assert cmd_type == "command"
|
|
16
|
+
assert args == ["help"]
|
|
17
|
+
|
|
18
|
+
def test_parse_command_with_arguments(self):
|
|
19
|
+
"""Test parsing a command with arguments."""
|
|
20
|
+
cmd_type, args = CommandParser.parse("/search python")
|
|
21
|
+
assert cmd_type == "command"
|
|
22
|
+
assert args == ["search", "python"]
|
|
23
|
+
|
|
24
|
+
def test_parse_command_with_multiple_arguments(self):
|
|
25
|
+
"""Test parsing a command with multiple arguments."""
|
|
26
|
+
cmd_type, args = CommandParser.parse("/search python lists")
|
|
27
|
+
assert cmd_type == "command"
|
|
28
|
+
assert args == ["search", "python", "lists"]
|
|
29
|
+
|
|
30
|
+
def test_parse_topic_name(self):
|
|
31
|
+
"""Test parsing a topic name (no leading /)."""
|
|
32
|
+
cmd_type, args = CommandParser.parse("Lists")
|
|
33
|
+
assert cmd_type == "topic"
|
|
34
|
+
assert args == ["Lists"]
|
|
35
|
+
|
|
36
|
+
def test_parse_topic_with_spaces(self):
|
|
37
|
+
"""Test parsing a topic name with spaces."""
|
|
38
|
+
cmd_type, args = CommandParser.parse("For Loops")
|
|
39
|
+
assert cmd_type == "topic"
|
|
40
|
+
assert args == ["For Loops"]
|
|
41
|
+
|
|
42
|
+
def test_parse_quoted_arguments(self):
|
|
43
|
+
"""Test parsing command with quoted arguments."""
|
|
44
|
+
cmd_type, args = CommandParser.parse('/search "list comprehension"')
|
|
45
|
+
assert cmd_type == "command"
|
|
46
|
+
assert args == ["search", "list comprehension"]
|
|
47
|
+
|
|
48
|
+
def test_parse_empty_input_raises_error(self):
|
|
49
|
+
"""Test that empty input raises ValueError."""
|
|
50
|
+
with pytest.raises(ValueError):
|
|
51
|
+
CommandParser.parse("")
|
|
52
|
+
|
|
53
|
+
def test_parse_whitespace_only_raises_error(self):
|
|
54
|
+
"""Test that whitespace-only input raises ValueError."""
|
|
55
|
+
with pytest.raises(ValueError):
|
|
56
|
+
CommandParser.parse(" ")
|
|
57
|
+
|
|
58
|
+
def test_parse_invalid_command_raises_error(self):
|
|
59
|
+
"""Test that invalid command raises ValueError."""
|
|
60
|
+
with pytest.raises(ValueError):
|
|
61
|
+
CommandParser.parse("/invalid_command")
|
|
62
|
+
|
|
63
|
+
def test_parse_command_only_slash_raises_error(self):
|
|
64
|
+
"""Test that just "/" raises ValueError."""
|
|
65
|
+
with pytest.raises(ValueError):
|
|
66
|
+
CommandParser.parse("/")
|
|
67
|
+
|
|
68
|
+
def test_parse_command_case_insensitive(self):
|
|
69
|
+
"""Test that commands are case-insensitive."""
|
|
70
|
+
cmd_type1, args1 = CommandParser.parse("/HELP")
|
|
71
|
+
cmd_type2, args2 = CommandParser.parse("/Help")
|
|
72
|
+
cmd_type3, args3 = CommandParser.parse("/help")
|
|
73
|
+
|
|
74
|
+
assert args1 == args2 == args3 == ["help"]
|
|
75
|
+
|
|
76
|
+
def test_parse_topic_case_preserved(self):
|
|
77
|
+
"""Test that topic names preserve case."""
|
|
78
|
+
cmd_type, args = CommandParser.parse("Lists")
|
|
79
|
+
assert args == ["Lists"]
|
|
80
|
+
|
|
81
|
+
cmd_type, args = CommandParser.parse("lists")
|
|
82
|
+
assert args == ["lists"]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TestCommandParserEdgeCases:
|
|
86
|
+
"""Test edge cases in command parsing."""
|
|
87
|
+
|
|
88
|
+
def test_parse_command_with_leading_trailing_spaces(self):
|
|
89
|
+
"""Test parsing command with leading/trailing spaces."""
|
|
90
|
+
cmd_type, args = CommandParser.parse(" /help ")
|
|
91
|
+
assert cmd_type == "command"
|
|
92
|
+
assert args == ["help"]
|
|
93
|
+
|
|
94
|
+
def test_parse_topic_with_leading_trailing_spaces(self):
|
|
95
|
+
"""Test parsing topic with leading/trailing spaces."""
|
|
96
|
+
cmd_type, args = CommandParser.parse(" Lists ")
|
|
97
|
+
assert cmd_type == "topic"
|
|
98
|
+
assert args == ["Lists"]
|
|
99
|
+
|
|
100
|
+
def test_parse_command_with_extra_spaces_between_args(self):
|
|
101
|
+
"""Test parsing command with extra spaces between arguments."""
|
|
102
|
+
cmd_type, args = CommandParser.parse("/search python lists")
|
|
103
|
+
assert cmd_type == "command"
|
|
104
|
+
assert args == ["search", "python", "lists"]
|
|
105
|
+
|
|
106
|
+
def test_parse_command_with_special_characters_in_args(self):
|
|
107
|
+
"""Test parsing command with special characters in arguments."""
|
|
108
|
+
cmd_type, args = CommandParser.parse("/search list[0]")
|
|
109
|
+
assert cmd_type == "command"
|
|
110
|
+
assert args == ["search", "list[0]"]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestCommandParserUtilityMethods:
|
|
114
|
+
"""Test utility methods of CommandParser."""
|
|
115
|
+
|
|
116
|
+
def test_is_command_true(self):
|
|
117
|
+
"""Test is_command returns True for commands."""
|
|
118
|
+
assert CommandParser.is_command("/help") is True
|
|
119
|
+
assert CommandParser.is_command("/search python") is True
|
|
120
|
+
|
|
121
|
+
def test_is_command_false(self):
|
|
122
|
+
"""Test is_command returns False for non-commands."""
|
|
123
|
+
assert CommandParser.is_command("Lists") is False
|
|
124
|
+
assert CommandParser.is_command("python") is False
|
|
125
|
+
|
|
126
|
+
def test_extract_command_name(self):
|
|
127
|
+
"""Test extracting command name from command string."""
|
|
128
|
+
assert CommandParser.extract_command_name("/help") == "help"
|
|
129
|
+
assert CommandParser.extract_command_name("/SEARCH") == "search"
|
|
130
|
+
assert CommandParser.extract_command_name("/search python") == "search"
|
|
131
|
+
|
|
132
|
+
def test_extract_command_name_invalid_input(self):
|
|
133
|
+
"""Test extract_command_name with invalid input."""
|
|
134
|
+
with pytest.raises(ValueError):
|
|
135
|
+
CommandParser.extract_command_name("Lists")
|
|
136
|
+
|
|
137
|
+
def test_normalize_topic_name(self):
|
|
138
|
+
"""Test normalizing topic names."""
|
|
139
|
+
assert CommandParser.normalize_topic_name("Lists") == "Lists"
|
|
140
|
+
assert CommandParser.normalize_topic_name(" Lists ") == "Lists"
|
|
141
|
+
assert CommandParser.normalize_topic_name("For Loops") == "For Loops"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestCommandParserAllValidCommands:
|
|
145
|
+
"""Test that all valid commands can be parsed."""
|
|
146
|
+
|
|
147
|
+
def test_all_valid_commands(self):
|
|
148
|
+
"""Test parsing all valid commands."""
|
|
149
|
+
valid_commands = [
|
|
150
|
+
"help", "list", "search", "random", "categories", "category",
|
|
151
|
+
"path", "related", "progress", "stats", "hint", "tip", "tips",
|
|
152
|
+
"run", "modify", "exit_edit", "history", "clear_history", "session",
|
|
153
|
+
"reset_progress", "commands", "about", "tutorial", "next", "prev",
|
|
154
|
+
"goto", "exit", "quit"
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
for cmd in valid_commands:
|
|
158
|
+
cmd_type, args = CommandParser.parse(f"/{cmd}")
|
|
159
|
+
assert cmd_type == "command"
|
|
160
|
+
assert args[0] == cmd
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Property-based tests for CommandParser using Hypothesis.
|
|
3
|
+
|
|
4
|
+
**Validates: Requirements 1.2, 1.3**
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from hypothesis import given, strategies as st, assume
|
|
9
|
+
from fishertools.learn.repl.command_parser import CommandParser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestCommandParserProperties:
|
|
13
|
+
"""Property-based tests for command parsing consistency."""
|
|
14
|
+
|
|
15
|
+
@given(st.text(min_size=1))
|
|
16
|
+
def test_parse_consistency(self, input_str):
|
|
17
|
+
"""
|
|
18
|
+
Property 1: Command Parsing Consistency
|
|
19
|
+
|
|
20
|
+
For any user input string, parsing it should consistently identify
|
|
21
|
+
the same command type and arguments across multiple invocations.
|
|
22
|
+
|
|
23
|
+
**Validates: Requirements 1.2, 1.3**
|
|
24
|
+
"""
|
|
25
|
+
# Skip empty or whitespace-only strings
|
|
26
|
+
if not input_str or not input_str.strip():
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
# Parse the same input twice
|
|
31
|
+
result1 = CommandParser.parse(input_str)
|
|
32
|
+
result2 = CommandParser.parse(input_str)
|
|
33
|
+
|
|
34
|
+
# Results should be identical
|
|
35
|
+
assert result1 == result2, f"Parsing inconsistent for input: {input_str}"
|
|
36
|
+
except ValueError:
|
|
37
|
+
# If parsing fails, it should fail consistently
|
|
38
|
+
with pytest.raises(ValueError):
|
|
39
|
+
CommandParser.parse(input_str)
|
|
40
|
+
|
|
41
|
+
@given(st.text(min_size=1, max_size=100))
|
|
42
|
+
def test_command_type_is_valid(self, input_str):
|
|
43
|
+
"""
|
|
44
|
+
For any valid input, the command type should be one of the valid types.
|
|
45
|
+
|
|
46
|
+
**Validates: Requirements 1.2, 1.3**
|
|
47
|
+
"""
|
|
48
|
+
if not input_str or not input_str.strip():
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
cmd_type, args = CommandParser.parse(input_str)
|
|
53
|
+
assert cmd_type in ["command", "topic"], f"Invalid command type: {cmd_type}"
|
|
54
|
+
assert isinstance(args, list), "Arguments should be a list"
|
|
55
|
+
assert len(args) > 0, "Arguments list should not be empty"
|
|
56
|
+
except ValueError:
|
|
57
|
+
# Invalid input is acceptable
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@given(st.text(min_size=1))
|
|
61
|
+
def test_parse_returns_tuple(self, input_str):
|
|
62
|
+
"""
|
|
63
|
+
For any valid input, parse should return a tuple of (str, list).
|
|
64
|
+
|
|
65
|
+
**Validates: Requirements 1.2, 1.3**
|
|
66
|
+
"""
|
|
67
|
+
if not input_str or not input_str.strip():
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
result = CommandParser.parse(input_str)
|
|
72
|
+
assert isinstance(result, tuple), "Result should be a tuple"
|
|
73
|
+
assert len(result) == 2, "Result should have 2 elements"
|
|
74
|
+
assert isinstance(result[0], str), "First element should be string"
|
|
75
|
+
assert isinstance(result[1], list), "Second element should be list"
|
|
76
|
+
except ValueError:
|
|
77
|
+
# Invalid input is acceptable
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@given(st.text(min_size=1))
|
|
81
|
+
def test_command_parsing_idempotent(self, input_str):
|
|
82
|
+
"""
|
|
83
|
+
For any input that parses successfully, parsing it multiple times
|
|
84
|
+
should always produce the same result.
|
|
85
|
+
|
|
86
|
+
**Validates: Requirements 1.2, 1.3**
|
|
87
|
+
"""
|
|
88
|
+
if not input_str or not input_str.strip():
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
results = [CommandParser.parse(input_str) for _ in range(3)]
|
|
93
|
+
# All results should be identical
|
|
94
|
+
assert all(r == results[0] for r in results), \
|
|
95
|
+
f"Parsing not idempotent for: {input_str}"
|
|
96
|
+
except ValueError:
|
|
97
|
+
# If it fails once, it should always fail
|
|
98
|
+
for _ in range(3):
|
|
99
|
+
with pytest.raises(ValueError):
|
|
100
|
+
CommandParser.parse(input_str)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the REPLEngine class.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import tempfile
|
|
7
|
+
from unittest.mock import patch, MagicMock
|
|
8
|
+
from fishertools.learn.knowledge_engine import KnowledgeEngine
|
|
9
|
+
from fishertools.learn.repl.engine import REPLEngine
|
|
10
|
+
from fishertools.learn.repl.session_manager import SessionManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def engine():
|
|
15
|
+
"""Fixture to provide a Knowledge Engine instance."""
|
|
16
|
+
return KnowledgeEngine()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def session_manager():
|
|
21
|
+
"""Fixture to provide a SessionManager instance."""
|
|
22
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
23
|
+
yield SessionManager(tmpdir)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def repl_engine(engine, session_manager):
|
|
28
|
+
"""Fixture to provide a REPLEngine instance."""
|
|
29
|
+
return REPLEngine(engine, session_manager)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestREPLEngineInitialization:
|
|
33
|
+
"""Test REPL engine initialization."""
|
|
34
|
+
|
|
35
|
+
def test_create_repl_engine(self, repl_engine):
|
|
36
|
+
"""Test creating a REPL engine."""
|
|
37
|
+
assert repl_engine is not None
|
|
38
|
+
assert repl_engine.engine is not None
|
|
39
|
+
assert repl_engine.session_manager is not None
|
|
40
|
+
assert repl_engine.command_handler is not None
|
|
41
|
+
assert repl_engine.code_sandbox is not None
|
|
42
|
+
|
|
43
|
+
def test_repl_engine_default_initialization(self):
|
|
44
|
+
"""Test REPL engine with default initialization."""
|
|
45
|
+
engine = REPLEngine()
|
|
46
|
+
assert engine is not None
|
|
47
|
+
assert engine.engine is not None
|
|
48
|
+
assert engine.session_manager is not None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestREPLEngineTopicDisplay:
|
|
52
|
+
"""Test topic display functionality."""
|
|
53
|
+
|
|
54
|
+
def test_display_topic_sets_current_topic(self, repl_engine, engine):
|
|
55
|
+
"""Test that displaying a topic sets current topic."""
|
|
56
|
+
topics = engine.list_topics()
|
|
57
|
+
if topics:
|
|
58
|
+
repl_engine._display_topic(topics[0])
|
|
59
|
+
assert repl_engine.current_topic == topics[0]
|
|
60
|
+
|
|
61
|
+
def test_display_topic_marks_viewed(self, repl_engine, engine, session_manager):
|
|
62
|
+
"""Test that displaying a topic marks it as viewed."""
|
|
63
|
+
topics = engine.list_topics()
|
|
64
|
+
if topics:
|
|
65
|
+
repl_engine._display_topic(topics[0])
|
|
66
|
+
assert session_manager.is_topic_viewed(topics[0])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestREPLEngineNavigation:
|
|
70
|
+
"""Test navigation functionality."""
|
|
71
|
+
|
|
72
|
+
def test_navigate_next_from_first_topic(self, repl_engine, engine):
|
|
73
|
+
"""Test navigating to next topic from first topic."""
|
|
74
|
+
path = engine.get_learning_path()
|
|
75
|
+
if len(path) > 1:
|
|
76
|
+
repl_engine._display_topic(path[0])
|
|
77
|
+
repl_engine._navigate_next()
|
|
78
|
+
assert repl_engine.current_topic == path[1]
|
|
79
|
+
|
|
80
|
+
def test_navigate_prev_from_second_topic(self, repl_engine, engine):
|
|
81
|
+
"""Test navigating to previous topic from second topic."""
|
|
82
|
+
path = engine.get_learning_path()
|
|
83
|
+
if len(path) > 1:
|
|
84
|
+
repl_engine._display_topic(path[1])
|
|
85
|
+
repl_engine._navigate_prev()
|
|
86
|
+
assert repl_engine.current_topic == path[0]
|
|
87
|
+
|
|
88
|
+
def test_navigate_to_topic(self, repl_engine, engine):
|
|
89
|
+
"""Test navigating to a specific topic."""
|
|
90
|
+
topics = engine.list_topics()
|
|
91
|
+
if topics:
|
|
92
|
+
repl_engine._navigate_to_topic(topics[0])
|
|
93
|
+
assert repl_engine.current_topic == topics[0]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestREPLEngineCodeExecution:
|
|
97
|
+
"""Test code execution functionality."""
|
|
98
|
+
|
|
99
|
+
def test_run_example_marks_executed(self, repl_engine, engine, session_manager):
|
|
100
|
+
"""Test that running an example marks it as executed."""
|
|
101
|
+
topics = engine.list_topics()
|
|
102
|
+
if topics:
|
|
103
|
+
topic = engine.get_topic(topics[0])
|
|
104
|
+
examples = topic.get("examples", [])
|
|
105
|
+
if examples:
|
|
106
|
+
repl_engine._display_topic(topics[0])
|
|
107
|
+
repl_engine._run_example(1)
|
|
108
|
+
assert session_manager.is_example_executed(topics[0], 1)
|
|
109
|
+
|
|
110
|
+
def test_run_invalid_example_number(self, repl_engine, engine):
|
|
111
|
+
"""Test running an invalid example number."""
|
|
112
|
+
topics = engine.list_topics()
|
|
113
|
+
if topics:
|
|
114
|
+
repl_engine._display_topic(topics[0])
|
|
115
|
+
# Should not crash with invalid example number
|
|
116
|
+
repl_engine._run_example(999)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class TestREPLEngineEditMode:
|
|
120
|
+
"""Test edit mode functionality."""
|
|
121
|
+
|
|
122
|
+
def test_enter_edit_mode(self, repl_engine, engine):
|
|
123
|
+
"""Test entering edit mode."""
|
|
124
|
+
topics = engine.list_topics()
|
|
125
|
+
if topics:
|
|
126
|
+
topic = engine.get_topic(topics[0])
|
|
127
|
+
examples = topic.get("examples", [])
|
|
128
|
+
if examples:
|
|
129
|
+
repl_engine._display_topic(topics[0])
|
|
130
|
+
repl_engine._enter_edit_mode(1)
|
|
131
|
+
assert repl_engine.in_edit_mode is True
|
|
132
|
+
assert repl_engine.edit_topic == topics[0]
|
|
133
|
+
assert repl_engine.edit_example_num == 1
|
|
134
|
+
|
|
135
|
+
def test_exit_edit_mode(self, repl_engine, engine):
|
|
136
|
+
"""Test exiting edit mode."""
|
|
137
|
+
topics = engine.list_topics()
|
|
138
|
+
if topics:
|
|
139
|
+
topic = engine.get_topic(topics[0])
|
|
140
|
+
examples = topic.get("examples", [])
|
|
141
|
+
if examples:
|
|
142
|
+
repl_engine._display_topic(topics[0])
|
|
143
|
+
repl_engine._enter_edit_mode(1)
|
|
144
|
+
repl_engine._exit_edit_mode()
|
|
145
|
+
assert repl_engine.in_edit_mode is False
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TestREPLEngineCommandHandling:
|
|
149
|
+
"""Test command handling."""
|
|
150
|
+
|
|
151
|
+
def test_handle_list_command(self, repl_engine):
|
|
152
|
+
"""Test handling /list command."""
|
|
153
|
+
# Should not crash
|
|
154
|
+
repl_engine._handle_command(["list"])
|
|
155
|
+
|
|
156
|
+
def test_handle_help_command(self, repl_engine):
|
|
157
|
+
"""Test handling /help command."""
|
|
158
|
+
# Should not crash
|
|
159
|
+
repl_engine._handle_command(["help"])
|
|
160
|
+
|
|
161
|
+
def test_handle_progress_command(self, repl_engine):
|
|
162
|
+
"""Test handling /progress command."""
|
|
163
|
+
# Should not crash
|
|
164
|
+
repl_engine._handle_command(["progress"])
|
|
165
|
+
|
|
166
|
+
def test_handle_exit_command(self, repl_engine):
|
|
167
|
+
"""Test handling /exit command."""
|
|
168
|
+
repl_engine._handle_command(["exit"])
|
|
169
|
+
assert repl_engine.running is False
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TestREPLEngineInputProcessing:
|
|
173
|
+
"""Test input processing."""
|
|
174
|
+
|
|
175
|
+
def test_process_command_input(self, repl_engine):
|
|
176
|
+
"""Test processing command input."""
|
|
177
|
+
# Should not crash
|
|
178
|
+
repl_engine._process_input("/help")
|
|
179
|
+
|
|
180
|
+
def test_process_topic_input(self, repl_engine, engine):
|
|
181
|
+
"""Test processing topic input."""
|
|
182
|
+
topics = engine.list_topics()
|
|
183
|
+
if topics:
|
|
184
|
+
# Should not crash
|
|
185
|
+
repl_engine._process_input(topics[0])
|
|
186
|
+
|
|
187
|
+
def test_process_invalid_input(self, repl_engine):
|
|
188
|
+
"""Test processing invalid input."""
|
|
189
|
+
# Should not crash
|
|
190
|
+
repl_engine._process_input("/invalid_command_xyz")
|