fishertools 0.2.1__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.
Files changed (81) hide show
  1. fishertools/__init__.py +82 -0
  2. fishertools/config/__init__.py +24 -0
  3. fishertools/config/manager.py +247 -0
  4. fishertools/config/models.py +96 -0
  5. fishertools/config/parser.py +265 -0
  6. fishertools/decorators.py +93 -0
  7. fishertools/documentation/__init__.py +38 -0
  8. fishertools/documentation/api.py +242 -0
  9. fishertools/documentation/generator.py +502 -0
  10. fishertools/documentation/models.py +126 -0
  11. fishertools/documentation/visual.py +583 -0
  12. fishertools/errors/__init__.py +29 -0
  13. fishertools/errors/exceptions.py +191 -0
  14. fishertools/errors/explainer.py +303 -0
  15. fishertools/errors/formatters.py +386 -0
  16. fishertools/errors/models.py +228 -0
  17. fishertools/errors/patterns.py +119 -0
  18. fishertools/errors/recovery.py +467 -0
  19. fishertools/examples/__init__.py +22 -0
  20. fishertools/examples/models.py +118 -0
  21. fishertools/examples/repository.py +770 -0
  22. fishertools/helpers.py +116 -0
  23. fishertools/integration.py +451 -0
  24. fishertools/learn/__init__.py +18 -0
  25. fishertools/learn/examples.py +550 -0
  26. fishertools/learn/tips.py +281 -0
  27. fishertools/learning/__init__.py +32 -0
  28. fishertools/learning/core.py +349 -0
  29. fishertools/learning/models.py +112 -0
  30. fishertools/learning/progress.py +314 -0
  31. fishertools/learning/session.py +500 -0
  32. fishertools/learning/tutorial.py +626 -0
  33. fishertools/legacy/__init__.py +76 -0
  34. fishertools/legacy/deprecated.py +261 -0
  35. fishertools/legacy/deprecation.py +149 -0
  36. fishertools/safe/__init__.py +16 -0
  37. fishertools/safe/collections.py +242 -0
  38. fishertools/safe/files.py +240 -0
  39. fishertools/safe/strings.py +15 -0
  40. fishertools/utils.py +57 -0
  41. fishertools-0.2.1.dist-info/METADATA +256 -0
  42. fishertools-0.2.1.dist-info/RECORD +81 -0
  43. fishertools-0.2.1.dist-info/WHEEL +5 -0
  44. fishertools-0.2.1.dist-info/licenses/LICENSE +21 -0
  45. fishertools-0.2.1.dist-info/top_level.txt +2 -0
  46. tests/__init__.py +6 -0
  47. tests/conftest.py +25 -0
  48. tests/test_config/__init__.py +3 -0
  49. tests/test_config/test_basic_config.py +57 -0
  50. tests/test_config/test_config_error_handling.py +287 -0
  51. tests/test_config/test_config_properties.py +435 -0
  52. tests/test_documentation/__init__.py +3 -0
  53. tests/test_documentation/test_documentation_properties.py +253 -0
  54. tests/test_documentation/test_visual_documentation_properties.py +444 -0
  55. tests/test_errors/__init__.py +3 -0
  56. tests/test_errors/test_api.py +301 -0
  57. tests/test_errors/test_error_handling.py +354 -0
  58. tests/test_errors/test_explainer.py +173 -0
  59. tests/test_errors/test_formatters.py +338 -0
  60. tests/test_errors/test_models.py +248 -0
  61. tests/test_errors/test_patterns.py +270 -0
  62. tests/test_examples/__init__.py +3 -0
  63. tests/test_examples/test_example_repository_properties.py +204 -0
  64. tests/test_examples/test_specific_examples.py +303 -0
  65. tests/test_integration.py +298 -0
  66. tests/test_integration_enhancements.py +462 -0
  67. tests/test_learn/__init__.py +3 -0
  68. tests/test_learn/test_examples.py +221 -0
  69. tests/test_learn/test_tips.py +285 -0
  70. tests/test_learning/__init__.py +3 -0
  71. tests/test_learning/test_interactive_learning_properties.py +337 -0
  72. tests/test_learning/test_learning_system_properties.py +194 -0
  73. tests/test_learning/test_progress_tracking_properties.py +279 -0
  74. tests/test_legacy/__init__.py +3 -0
  75. tests/test_legacy/test_backward_compatibility.py +236 -0
  76. tests/test_legacy/test_deprecation_warnings.py +208 -0
  77. tests/test_safe/__init__.py +3 -0
  78. tests/test_safe/test_collections_properties.py +189 -0
  79. tests/test_safe/test_files.py +104 -0
  80. tests/test_structure.py +58 -0
  81. tests/test_structure_enhancements.py +115 -0
@@ -0,0 +1,173 @@
1
+ """
2
+ Tests for ErrorExplainer class and exception handling.
3
+ """
4
+
5
+ import pytest
6
+ from hypothesis import given, strategies as st
7
+
8
+ from fishertools.errors.explainer import ErrorExplainer
9
+ from fishertools.errors.models import ExplainerConfig
10
+
11
+
12
+ # Common Python exception types for testing
13
+ COMMON_EXCEPTION_TYPES = [
14
+ TypeError, ValueError, AttributeError, IndexError, KeyError, ImportError, SyntaxError
15
+ ]
16
+
17
+ # Custom exception types for fallback testing
18
+ class CustomError(Exception):
19
+ """Custom exception for testing fallback behavior."""
20
+ pass
21
+
22
+ class AnotherCustomError(Exception):
23
+ """Another custom exception for testing."""
24
+ pass
25
+
26
+
27
+ @pytest.mark.property
28
+ class TestComprehensiveExceptionSupport:
29
+ """Property tests for comprehensive exception support."""
30
+
31
+ @given(
32
+ exception_type=st.sampled_from(COMMON_EXCEPTION_TYPES),
33
+ error_message=st.text(min_size=1, max_size=200)
34
+ )
35
+ def test_comprehensive_exception_support(self, exception_type, error_message):
36
+ """
37
+ Property 4: Comprehensive Exception Support
38
+ For any common Python exception type (TypeError, ValueError, AttributeError,
39
+ IndexError, KeyError, ImportError, SyntaxError), the Error_Explainer should
40
+ provide a specific, contextual explanation.
41
+
42
+ Feature: fishertools-refactor, Property 4: Comprehensive Exception Support
43
+ Validates: Requirements 2.4, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7
44
+ """
45
+ # Create exception instance
46
+ exception = exception_type(error_message)
47
+
48
+ # Create explainer
49
+ explainer = ErrorExplainer()
50
+
51
+ # Get explanation
52
+ explanation = explainer.explain(exception)
53
+
54
+ # Property: Explanation must be generated without errors
55
+ assert explanation is not None
56
+
57
+ # Property: Explanation must contain all required components
58
+ assert explanation.original_error is not None
59
+ assert explanation.error_type is not None
60
+ assert explanation.simple_explanation is not None
61
+ assert explanation.fix_tip is not None
62
+ assert explanation.code_example is not None
63
+
64
+ # Property: Error type must match the exception type
65
+ assert explanation.error_type == exception_type.__name__
66
+
67
+ # Property: Original error must contain the exception message
68
+ assert error_message in explanation.original_error or str(exception) == explanation.original_error
69
+
70
+ # Property: Explanation must be non-empty and meaningful
71
+ assert len(explanation.simple_explanation.strip()) > 0
72
+ assert len(explanation.fix_tip.strip()) > 0
73
+ assert len(explanation.code_example.strip()) > 0
74
+
75
+ # Property: Explanation should be in Russian (contains Cyrillic characters)
76
+ cyrillic_chars = set('абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')
77
+ explanation_text = explanation.simple_explanation + explanation.fix_tip
78
+ assert any(char in cyrillic_chars for char in explanation_text)
79
+
80
+
81
+ @pytest.mark.property
82
+ class TestGracefulFallbackForUnknownExceptions:
83
+ """Property tests for graceful fallback behavior."""
84
+
85
+ @given(
86
+ error_message=st.text(min_size=1, max_size=200)
87
+ )
88
+ def test_graceful_fallback_for_unknown_exceptions(self, error_message):
89
+ """
90
+ Property 5: Graceful Fallback for Unknown Exceptions
91
+ For any unsupported or custom exception type, the Error_Explainer should
92
+ provide a generic but helpful message instead of failing.
93
+
94
+ Feature: fishertools-refactor, Property 5: Graceful Fallback for Unknown Exceptions
95
+ Validates: Requirements 2.5
96
+ """
97
+ # Create custom exception (not in common types)
98
+ exception = CustomError(error_message)
99
+
100
+ # Create explainer
101
+ explainer = ErrorExplainer()
102
+
103
+ # Get explanation - should not raise any exceptions
104
+ explanation = explainer.explain(exception)
105
+
106
+ # Property: Explanation must be generated without errors
107
+ assert explanation is not None
108
+
109
+ # Property: Explanation must contain all required components
110
+ assert explanation.original_error is not None
111
+ assert explanation.error_type is not None
112
+ assert explanation.simple_explanation is not None
113
+ assert explanation.fix_tip is not None
114
+ assert explanation.code_example is not None
115
+
116
+ # Property: Error type must match the custom exception type
117
+ assert explanation.error_type == "CustomError"
118
+
119
+ # Property: Original error must contain the exception message
120
+ assert error_message in explanation.original_error or str(exception) == explanation.original_error
121
+
122
+ # Property: Fallback explanation must be helpful and non-empty
123
+ assert len(explanation.simple_explanation.strip()) > 0
124
+ assert len(explanation.fix_tip.strip()) > 0
125
+ assert len(explanation.code_example.strip()) > 0
126
+
127
+ # Property: Fallback should mention the error type
128
+ assert "CustomError" in explanation.simple_explanation or "CustomError" in explanation.code_example
129
+
130
+ # Property: Fallback should be in Russian
131
+ cyrillic_chars = set('абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')
132
+ explanation_text = explanation.simple_explanation + explanation.fix_tip
133
+ assert any(char in cyrillic_chars for char in explanation_text)
134
+
135
+ # Property: Additional info should provide guidance
136
+ if explanation.additional_info:
137
+ assert len(explanation.additional_info.strip()) > 0
138
+
139
+
140
+ class TestExplainerUnitTests:
141
+ """Unit tests for specific explainer functionality."""
142
+
143
+ def test_explainer_with_config(self):
144
+ """Test explainer initialization with custom config."""
145
+ config = ExplainerConfig(language='en', use_colors=False)
146
+ explainer = ErrorExplainer(config)
147
+
148
+ assert explainer.config.language == 'en'
149
+ assert explainer.config.use_colors is False
150
+
151
+ def test_explainer_fallback_behavior(self):
152
+ """Test specific fallback behavior with known custom exception."""
153
+ exception = AnotherCustomError("test message")
154
+ explainer = ErrorExplainer()
155
+
156
+ explanation = explainer.explain(exception)
157
+
158
+ assert explanation.error_type == "AnotherCustomError"
159
+ assert "test message" in explanation.original_error
160
+ assert "AnotherCustomError" in explanation.simple_explanation
161
+
162
+ def test_explainer_handles_empty_patterns(self):
163
+ """Test that explainer works even with no patterns loaded."""
164
+ explainer = ErrorExplainer()
165
+ # Patterns list should be empty since no patterns are defined yet
166
+ assert isinstance(explainer.patterns, list)
167
+
168
+ # Should still handle exceptions gracefully
169
+ exception = TypeError("test error")
170
+ explanation = explainer.explain(exception)
171
+
172
+ assert explanation is not None
173
+ assert explanation.error_type == "TypeError"
@@ -0,0 +1,338 @@
1
+ """
2
+ Property-based tests for output formatting system.
3
+
4
+ Tests Properties 10, 11, and 12 from the design document.
5
+ """
6
+
7
+ import pytest
8
+ from hypothesis import given, strategies as st, assume
9
+ import re
10
+ from fishertools.errors.formatters import (
11
+ ConsoleFormatter, PlainFormatter, JsonFormatter, get_formatter, Colors
12
+ )
13
+ from fishertools.errors.models import ErrorExplanation
14
+
15
+
16
+ class TestConsoleFormatter:
17
+ """Tests for ConsoleFormatter with color support."""
18
+
19
+ def test_formatter_initialization(self):
20
+ """Test formatter can be initialized with different color settings."""
21
+ formatter_with_colors = ConsoleFormatter(use_colors=True)
22
+ formatter_without_colors = ConsoleFormatter(use_colors=False)
23
+
24
+ assert isinstance(formatter_with_colors, ConsoleFormatter)
25
+ assert isinstance(formatter_without_colors, ConsoleFormatter)
26
+
27
+ @given(
28
+ original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
29
+ error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
30
+ simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
31
+ fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
32
+ code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
33
+ additional_info=st.one_of(st.none(), st.text(min_size=0, max_size=200).filter(lambda x: '\r' not in x and '\n' not in x))
34
+ )
35
+ def test_structured_output_format(self, original_error, error_type, simple_explanation,
36
+ fix_tip, code_example, additional_info):
37
+ """
38
+ **Property 10: Structured Output Format**
39
+ For any error explanation output, it should contain clearly delineated
40
+ sections for explanation, tip, example, and original error message.
41
+ **Validates: Requirements 7.1, 7.3**
42
+ """
43
+ explanation = ErrorExplanation(
44
+ original_error=original_error,
45
+ error_type=error_type,
46
+ simple_explanation=simple_explanation,
47
+ fix_tip=fix_tip,
48
+ code_example=code_example,
49
+ additional_info=additional_info
50
+ )
51
+
52
+ formatter = ConsoleFormatter(use_colors=False) # Test without colors for clarity
53
+ output = formatter.format(explanation)
54
+
55
+ # Check that output contains structured sections
56
+ assert "Ошибка Python:" in output
57
+ assert "=== Что это означает ===" in output
58
+ assert "=== Как исправить ===" in output
59
+ assert "=== Пример ===" in output
60
+
61
+ # Check that the content appears in the output (normalize whitespace for comparison)
62
+ normalized_output = ' '.join(output.split())
63
+ normalized_explanation = ' '.join(simple_explanation.split())
64
+ normalized_tip = ' '.join(fix_tip.split())
65
+ normalized_code = ' '.join(code_example.split())
66
+
67
+ assert normalized_explanation in normalized_output
68
+ assert normalized_tip in normalized_output
69
+ assert normalized_code in normalized_output
70
+
71
+ # If original error is not empty, it should appear
72
+ if original_error.strip():
73
+ assert "=== Сообщение об ошибке ===" in output
74
+ normalized_error = ' '.join(original_error.split())
75
+ assert normalized_error in normalized_output
76
+
77
+ # If additional info is provided, it should appear
78
+ if additional_info and additional_info.strip():
79
+ assert "=== Дополнительная информация ===" in output
80
+ normalized_info = ' '.join(additional_info.split())
81
+ assert normalized_info in normalized_output
82
+
83
+ @given(
84
+ original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
85
+ error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
86
+ simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
87
+ fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
88
+ code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
89
+ use_colors=st.booleans()
90
+ )
91
+ def test_enhanced_output_readability(self, original_error, error_type, simple_explanation,
92
+ fix_tip, code_example, use_colors):
93
+ """
94
+ **Property 11: Enhanced Output Readability**
95
+ For any error explanation output, it should include formatting elements
96
+ (colors, highlighting, or other visual enhancements) to improve readability.
97
+ **Validates: Requirements 7.2, 7.4**
98
+ """
99
+ explanation = ErrorExplanation(
100
+ original_error=original_error,
101
+ error_type=error_type,
102
+ simple_explanation=simple_explanation,
103
+ fix_tip=fix_tip,
104
+ code_example=code_example
105
+ )
106
+
107
+ formatter = ConsoleFormatter(use_colors=use_colors)
108
+ output = formatter.format(explanation)
109
+
110
+ # Check for visual enhancement elements
111
+ if use_colors and formatter.use_colors:
112
+ # Should contain ANSI color codes for enhanced readability
113
+ ansi_pattern = r'\033\[[0-9;]*m'
114
+ assert re.search(ansi_pattern, output), "Output should contain ANSI color codes when colors enabled"
115
+
116
+ # Should contain visual separators and structure
117
+ assert "═══" in output or "===" in output, "Output should contain section separators"
118
+ assert "🚨" in output, "Output should contain emoji for visual appeal"
119
+ assert "💡" in output, "Output should contain tip emoji"
120
+
121
+ # Code blocks should have visual boundaries
122
+ if code_example.strip():
123
+ assert "┌─" in output and "└─" in output, "Code examples should have visual boundaries"
124
+
125
+ @given(
126
+ code_example=st.text(min_size=1, max_size=500).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
127
+ error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
128
+ simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
129
+ fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x)
130
+ )
131
+ def test_proper_code_example_formatting(self, code_example, error_type,
132
+ simple_explanation, fix_tip):
133
+ """
134
+ **Property 12: Proper Code Example Formatting**
135
+ For any code example in error explanations, it should be formatted
136
+ with proper indentation and syntax highlighting.
137
+ **Validates: Requirements 7.5**
138
+ """
139
+ explanation = ErrorExplanation(
140
+ original_error="test error",
141
+ error_type=error_type,
142
+ simple_explanation=simple_explanation,
143
+ fix_tip=fix_tip,
144
+ code_example=code_example
145
+ )
146
+
147
+ formatter = ConsoleFormatter(use_colors=True)
148
+ output = formatter.format(explanation)
149
+
150
+ # Check for proper code block formatting
151
+ assert "┌─ Пример кода ─┐" in output, "Code should have proper header"
152
+ assert "└────────────────┘" in output, "Code should have proper footer"
153
+
154
+ # Check for indentation - code lines should be indented
155
+ code_section_start = output.find("┌─ Пример кода ─┐")
156
+ code_section_end = output.find("└────────────────┘", code_section_start)
157
+
158
+ if code_section_start != -1 and code_section_end != -1:
159
+ code_section = output[code_section_start:code_section_end]
160
+ code_lines = code_section.split('\n')[1:] # Skip header line
161
+
162
+ # Non-empty code lines should be indented
163
+ for line in code_lines:
164
+ if line.strip(): # Only check non-empty lines
165
+ assert line.startswith(' '), f"Code line should be indented: '{line}'"
166
+
167
+
168
+ class TestPlainFormatter:
169
+ """Tests for PlainFormatter without colors."""
170
+
171
+ @given(
172
+ original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
173
+ error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
174
+ simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
175
+ fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
176
+ code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x)
177
+ )
178
+ def test_plain_formatter_structure(self, original_error, error_type, simple_explanation,
179
+ fix_tip, code_example):
180
+ """Test that PlainFormatter produces structured output without colors."""
181
+ # Filter out any ANSI escape sequences from input
182
+ import re
183
+ ansi_pattern = r'\033\[[0-9;]*m'
184
+
185
+ original_error = re.sub(ansi_pattern, '', original_error)
186
+ error_type = re.sub(ansi_pattern, '', error_type)
187
+ simple_explanation = re.sub(ansi_pattern, '', simple_explanation)
188
+ fix_tip = re.sub(ansi_pattern, '', fix_tip)
189
+ code_example = re.sub(ansi_pattern, '', code_example)
190
+
191
+ # Ensure strings are not empty after cleaning
192
+ if not error_type.strip():
193
+ error_type = "TestError"
194
+ if not simple_explanation.strip():
195
+ simple_explanation = "Test explanation"
196
+ if not fix_tip.strip():
197
+ fix_tip = "Test tip"
198
+ if not code_example.strip():
199
+ code_example = "# test code"
200
+
201
+ explanation = ErrorExplanation(
202
+ original_error=original_error,
203
+ error_type=error_type,
204
+ simple_explanation=simple_explanation,
205
+ fix_tip=fix_tip,
206
+ code_example=code_example
207
+ )
208
+
209
+ formatter = PlainFormatter()
210
+ output = formatter.format(explanation)
211
+
212
+ # Should not contain ANSI color codes
213
+ assert not re.search(ansi_pattern, output), "Plain formatter should not contain color codes"
214
+
215
+ # Should contain structured sections
216
+ assert "Ошибка Python:" in output
217
+ assert "Что это означает:" in output
218
+ assert "Как исправить:" in output
219
+ assert "Пример:" in output
220
+
221
+ # Content should be present (normalize whitespace for comparison)
222
+ normalized_output = ' '.join(output.split())
223
+ normalized_explanation = ' '.join(simple_explanation.split())
224
+ normalized_tip = ' '.join(fix_tip.split())
225
+ normalized_code = ' '.join(code_example.split())
226
+
227
+ assert normalized_explanation in normalized_output
228
+ assert normalized_tip in normalized_output
229
+ assert normalized_code in normalized_output
230
+
231
+
232
+ class TestJsonFormatter:
233
+ """Tests for JsonFormatter."""
234
+
235
+ @given(
236
+ original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
237
+ error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
238
+ simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
239
+ fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
240
+ code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x)
241
+ )
242
+ def test_json_formatter_structure(self, original_error, error_type, simple_explanation,
243
+ fix_tip, code_example):
244
+ """Test that JsonFormatter produces valid JSON output."""
245
+ explanation = ErrorExplanation(
246
+ original_error=original_error,
247
+ error_type=error_type,
248
+ simple_explanation=simple_explanation,
249
+ fix_tip=fix_tip,
250
+ code_example=code_example
251
+ )
252
+
253
+ formatter = JsonFormatter()
254
+ output = formatter.format(explanation)
255
+
256
+ # Should be valid JSON
257
+ import json
258
+ parsed = json.loads(output)
259
+
260
+ # Should contain all required fields
261
+ assert parsed['original_error'] == original_error
262
+ assert parsed['error_type'] == error_type
263
+ assert parsed['simple_explanation'] == simple_explanation
264
+ assert parsed['fix_tip'] == fix_tip
265
+ assert parsed['code_example'] == code_example
266
+
267
+
268
+ class TestFormatterFactory:
269
+ """Tests for get_formatter factory function."""
270
+
271
+ def test_get_formatter_console(self):
272
+ """Test getting console formatter."""
273
+ formatter = get_formatter('console')
274
+ assert isinstance(formatter, ConsoleFormatter)
275
+
276
+ formatter_no_colors = get_formatter('console', use_colors=False)
277
+ assert isinstance(formatter_no_colors, ConsoleFormatter)
278
+ assert not formatter_no_colors.use_colors or not formatter_no_colors._supports_color()
279
+
280
+ def test_get_formatter_plain(self):
281
+ """Test getting plain formatter."""
282
+ formatter = get_formatter('plain')
283
+ assert isinstance(formatter, PlainFormatter)
284
+
285
+ def test_get_formatter_json(self):
286
+ """Test getting JSON formatter."""
287
+ formatter = get_formatter('json')
288
+ assert isinstance(formatter, JsonFormatter)
289
+
290
+ def test_get_formatter_invalid(self):
291
+ """Test getting invalid formatter raises error."""
292
+ from fishertools.errors.exceptions import FormattingError
293
+ with pytest.raises(FormattingError, match="Неподдерживаемый тип форматтера"):
294
+ get_formatter('invalid')
295
+
296
+
297
+ class TestColorSupport:
298
+ """Tests for color support functionality."""
299
+
300
+ def test_colors_class_constants(self):
301
+ """Test that Colors class has required constants."""
302
+ assert hasattr(Colors, 'RED')
303
+ assert hasattr(Colors, 'GREEN')
304
+ assert hasattr(Colors, 'YELLOW')
305
+ assert hasattr(Colors, 'BLUE')
306
+ assert hasattr(Colors, 'BOLD')
307
+ assert hasattr(Colors, 'RESET')
308
+
309
+ # All color codes should be strings
310
+ assert isinstance(Colors.RED, str)
311
+ assert isinstance(Colors.RESET, str)
312
+
313
+ def test_colorize_functionality(self):
314
+ """Test colorize method works correctly."""
315
+ formatter = ConsoleFormatter(use_colors=True)
316
+
317
+ # Test colorizing text
318
+ colored_text = formatter._colorize("test", Colors.RED)
319
+ if formatter.use_colors:
320
+ assert Colors.RED in colored_text
321
+ assert Colors.RESET in colored_text
322
+ assert "test" in colored_text
323
+ else:
324
+ assert colored_text == "test"
325
+
326
+ def test_syntax_highlighting(self):
327
+ """Test Python syntax highlighting."""
328
+ formatter = ConsoleFormatter(use_colors=True)
329
+
330
+ code_line = " def test_function():"
331
+ highlighted = formatter._highlight_python_syntax(code_line)
332
+
333
+ if formatter.use_colors:
334
+ # Should contain color codes for 'def' keyword
335
+ assert Colors.MAGENTA in highlighted or "def" in highlighted
336
+
337
+ # Original text should still be present
338
+ assert "test_function" in highlighted