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.
Files changed (69) hide show
  1. fishertools/__init__.py +16 -5
  2. fishertools/errors/__init__.py +11 -3
  3. fishertools/errors/exception_types.py +282 -0
  4. fishertools/errors/explainer.py +87 -1
  5. fishertools/errors/models.py +73 -1
  6. fishertools/errors/patterns.py +40 -0
  7. fishertools/examples/cli_example.py +156 -0
  8. fishertools/examples/learn_example.py +65 -0
  9. fishertools/examples/logger_example.py +176 -0
  10. fishertools/examples/menu_example.py +101 -0
  11. fishertools/examples/storage_example.py +175 -0
  12. fishertools/input_utils.py +185 -0
  13. fishertools/learn/__init__.py +19 -2
  14. fishertools/learn/examples.py +88 -1
  15. fishertools/learn/knowledge_engine.py +321 -0
  16. fishertools/learn/repl/__init__.py +19 -0
  17. fishertools/learn/repl/cli.py +31 -0
  18. fishertools/learn/repl/code_sandbox.py +229 -0
  19. fishertools/learn/repl/command_handler.py +544 -0
  20. fishertools/learn/repl/command_parser.py +165 -0
  21. fishertools/learn/repl/engine.py +479 -0
  22. fishertools/learn/repl/models.py +121 -0
  23. fishertools/learn/repl/session_manager.py +284 -0
  24. fishertools/learn/repl/test_code_sandbox.py +261 -0
  25. fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
  26. fishertools/learn/repl/test_command_handler.py +224 -0
  27. fishertools/learn/repl/test_command_handler_pbt.py +189 -0
  28. fishertools/learn/repl/test_command_parser.py +160 -0
  29. fishertools/learn/repl/test_command_parser_pbt.py +100 -0
  30. fishertools/learn/repl/test_engine.py +190 -0
  31. fishertools/learn/repl/test_session_manager.py +310 -0
  32. fishertools/learn/repl/test_session_manager_pbt.py +182 -0
  33. fishertools/learn/test_knowledge_engine.py +241 -0
  34. fishertools/learn/test_knowledge_engine_pbt.py +180 -0
  35. fishertools/patterns/__init__.py +46 -0
  36. fishertools/patterns/cli.py +175 -0
  37. fishertools/patterns/logger.py +140 -0
  38. fishertools/patterns/menu.py +99 -0
  39. fishertools/patterns/storage.py +127 -0
  40. fishertools/readme_transformer.py +631 -0
  41. fishertools/safe/__init__.py +6 -1
  42. fishertools/safe/files.py +329 -1
  43. fishertools/transform_readme.py +105 -0
  44. fishertools-0.4.0.dist-info/METADATA +104 -0
  45. fishertools-0.4.0.dist-info/RECORD +131 -0
  46. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
  47. tests/test_documentation_properties.py +329 -0
  48. tests/test_documentation_structure.py +349 -0
  49. tests/test_errors/test_exception_types.py +446 -0
  50. tests/test_errors/test_exception_types_pbt.py +333 -0
  51. tests/test_errors/test_patterns.py +52 -0
  52. tests/test_input_utils/__init__.py +1 -0
  53. tests/test_input_utils/test_input_utils.py +65 -0
  54. tests/test_learn/test_examples.py +179 -1
  55. tests/test_learn/test_explain_properties.py +307 -0
  56. tests/test_patterns_cli.py +611 -0
  57. tests/test_patterns_docstrings.py +473 -0
  58. tests/test_patterns_logger.py +465 -0
  59. tests/test_patterns_menu.py +440 -0
  60. tests/test_patterns_storage.py +447 -0
  61. tests/test_readme_enhancements_v0_3_1.py +2036 -0
  62. tests/test_readme_transformer/__init__.py +1 -0
  63. tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
  64. tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
  65. tests/test_safe/test_files.py +726 -1
  66. fishertools-0.2.1.dist-info/METADATA +0 -256
  67. fishertools-0.2.1.dist-info/RECORD +0 -81
  68. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
  69. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,329 @@
1
+ """
2
+ Property-based tests for documentation structure and correctness.
3
+
4
+ These tests verify universal properties that should hold across all documentation files.
5
+ Uses Hypothesis for property-based testing.
6
+ """
7
+
8
+ import re
9
+ from pathlib import Path
10
+ from hypothesis import given, strategies as st
11
+
12
+
13
+ class TestDocumentationProperties:
14
+ """Property-based tests for documentation correctness."""
15
+
16
+ @given(st.just(None))
17
+ def test_property_1_all_sections_created(self, _):
18
+ """
19
+ **Validates: Requirements 1.1, 1.3**
20
+
21
+ Property 1: All documentation sections are created
22
+ For each of the 7 required sections (Getting Started, Features, Installation,
23
+ API Reference, Examples, Limitations, Contributing), the corresponding file
24
+ should exist in docs/ folder with .md extension.
25
+ """
26
+ docs_dir = Path("docs")
27
+ required_sections = [
28
+ "getting-started.md",
29
+ "features.md",
30
+ "installation.md",
31
+ "api-reference.md",
32
+ "examples.md",
33
+ "limitations.md",
34
+ "contributing.md",
35
+ ]
36
+
37
+ for section_file in required_sections:
38
+ filepath = docs_dir / section_file
39
+ assert filepath.exists(), f"Section file {section_file} does not exist"
40
+ assert filepath.suffix == ".md", f"Section file {section_file} is not markdown"
41
+
42
+ @given(st.just(None))
43
+ def test_property_2_all_sections_linked_from_readme(self, _):
44
+ """
45
+ **Validates: Requirements 2.3, 3.3, 4.3, 5.3, 6.3, 7.3, 8.3, 9.3, 11.3**
46
+
47
+ Property 2: All sections are accessible by links from README
48
+ For each of the 7 documentation sections in docs/, the main README.md
49
+ should contain a working link to that section.
50
+ """
51
+ with open("README.md", "r", encoding="utf-8") as f:
52
+ readme_content = f.read()
53
+
54
+ required_links = [
55
+ "docs/getting-started.md",
56
+ "docs/features.md",
57
+ "docs/installation.md",
58
+ "docs/api-reference.md",
59
+ "docs/examples.md",
60
+ "docs/limitations.md",
61
+ "docs/contributing.md",
62
+ ]
63
+
64
+ for link in required_links:
65
+ assert link in readme_content, f"README missing link to {link}"
66
+
67
+ @given(st.just(None))
68
+ def test_property_3_all_links_work(self, _):
69
+ """
70
+ **Validates: Requirements 11.1, 11.2**
71
+
72
+ Property 3: All links in documentation work
73
+ For any link in documentation files (in docs/ and main README),
74
+ that link should point to an existing file or section.
75
+ """
76
+ docs_dir = Path("docs")
77
+ doc_files = [
78
+ "index.md",
79
+ "getting-started.md",
80
+ "features.md",
81
+ "installation.md",
82
+ "api-reference.md",
83
+ "examples.md",
84
+ "limitations.md",
85
+ "contributing.md",
86
+ ]
87
+
88
+ # Pattern to find markdown links: [text](path)
89
+ link_pattern = r'\[([^\]]+)\]\(([^)]+)\)'
90
+
91
+ for filename in doc_files:
92
+ filepath = docs_dir / filename
93
+ with open(filepath, "r", encoding="utf-8") as f:
94
+ content = f.read()
95
+
96
+ # Find all links
97
+ links = re.findall(link_pattern, content)
98
+
99
+ for link_text, link_path in links:
100
+ # Skip external links (http, https, mailto)
101
+ if link_path.startswith(('http://', 'https://', 'mailto:')):
102
+ continue
103
+
104
+ # Skip anchor-only links
105
+ if link_path.startswith('#'):
106
+ continue
107
+
108
+ # Check if file exists
109
+ if link_path.startswith('../'):
110
+ # Relative path going up
111
+ target_path = Path(filepath.parent) / link_path
112
+ else:
113
+ # Relative path in same directory
114
+ target_path = Path(filepath.parent) / link_path
115
+
116
+ # Normalize path
117
+ target_path = target_path.resolve()
118
+
119
+ # For links like "index.md", check if file exists
120
+ if not link_path.startswith('#'):
121
+ assert target_path.exists() or target_path.with_suffix('').exists(), \
122
+ f"Link {link_path} in {filename} points to non-existent file"
123
+
124
+ @given(st.just(None))
125
+ def test_property_4_readme_brief_description(self, _):
126
+ """
127
+ **Validates: Requirements 9.1**
128
+
129
+ Property 4: Main README contains brief description
130
+ For the main README.md, the project description should contain
131
+ no more than 50 words.
132
+ """
133
+ with open("README.md", "r", encoding="utf-8") as f:
134
+ content = f.read()
135
+
136
+ # Extract the main description (first paragraph after title)
137
+ lines = content.split('\n')
138
+ description_lines = []
139
+ in_description = False
140
+
141
+ for line in lines:
142
+ if line.startswith('# '):
143
+ in_description = True
144
+ continue
145
+ if in_description:
146
+ if line.startswith('##') or line.startswith('```'):
147
+ break
148
+ if line.strip():
149
+ description_lines.append(line)
150
+
151
+ description = ' '.join(description_lines).strip()
152
+ word_count = len(description.split())
153
+
154
+ assert word_count <= 50, f"README description has {word_count} words, should be <= 50"
155
+
156
+ @given(st.just(None))
157
+ def test_property_5_readme_contains_all_section_links(self, _):
158
+ """
159
+ **Validates: Requirements 9.3, 11.3**
160
+
161
+ Property 5: Main README contains links to all sections
162
+ For the main README.md, it should contain links to all 7 documentation
163
+ sections (Getting Started, Features, Installation, API Reference,
164
+ Examples, Limitations, Contributing).
165
+ """
166
+ with open("README.md", "r", encoding="utf-8") as f:
167
+ content = f.read()
168
+
169
+ required_sections = [
170
+ "Getting Started",
171
+ "Features",
172
+ "Installation",
173
+ "API Reference",
174
+ "Examples",
175
+ "Limitations",
176
+ "Contributing",
177
+ ]
178
+
179
+ for section in required_sections:
180
+ assert section in content, f"README missing reference to {section}"
181
+
182
+ @given(st.just(None))
183
+ def test_property_6_each_section_links_to_index(self, _):
184
+ """
185
+ **Validates: Requirements 11.4**
186
+
187
+ Property 6: Each section contains link to main page
188
+ For each file in docs/ folder, that file should contain a link to
189
+ the main documentation page (index.md or README.md).
190
+ """
191
+ docs_dir = Path("docs")
192
+ doc_files = [
193
+ "getting-started.md",
194
+ "features.md",
195
+ "installation.md",
196
+ "api-reference.md",
197
+ "examples.md",
198
+ "limitations.md",
199
+ "contributing.md",
200
+ ]
201
+
202
+ for filename in doc_files:
203
+ filepath = docs_dir / filename
204
+ with open(filepath, "r", encoding="utf-8") as f:
205
+ content = f.read()
206
+
207
+ # Check for link back to index or documentation
208
+ has_return_link = (
209
+ "index.md" in content or
210
+ "Documentation Index" in content or
211
+ "Return to" in content or
212
+ "Back to" in content
213
+ )
214
+ assert has_return_link, f"{filename} missing return link to index"
215
+
216
+ @given(st.just(None))
217
+ def test_property_7_readme_no_duplication(self, _):
218
+ """
219
+ **Validates: Requirements 9.5, 10.3**
220
+
221
+ Property 7: Main README contains no duplicated information
222
+ For the main README.md, it should contain only brief information and links,
223
+ without detailed sections that are in docs/.
224
+ """
225
+ with open("README.md", "r", encoding="utf-8") as f:
226
+ readme_content = f.read()
227
+
228
+ # Check that README doesn't contain detailed sections
229
+ # that should be in docs/
230
+ detailed_sections = [
231
+ "🚨 Объяснение ошибок", # Russian
232
+ "🛡️ Безопасные утилиты", # Russian
233
+ "📚 Обучающие инструменты", # Russian
234
+ "🔧 Готовые паттерны", # Russian
235
+ "## 🎯 Основные возможности", # Russian
236
+ "### 🚨 Объяснение ошибок Python", # Russian
237
+ ]
238
+
239
+ # Count how many detailed sections are in README
240
+ detailed_count = sum(1 for section in detailed_sections if section in readme_content)
241
+
242
+ # README should have minimal detailed content (mostly links)
243
+ # Allow some basic feature descriptions but not full sections
244
+ assert detailed_count == 0, f"README contains {detailed_count} detailed sections that should be in docs/"
245
+
246
+ @given(st.just(None))
247
+ def test_property_all_files_have_content(self, _):
248
+ """
249
+ Property: All documentation files have substantial content
250
+ For each documentation file, it should have more than 100 bytes of content.
251
+ """
252
+ docs_dir = Path("docs")
253
+ doc_files = [
254
+ "index.md",
255
+ "getting-started.md",
256
+ "features.md",
257
+ "installation.md",
258
+ "api-reference.md",
259
+ "examples.md",
260
+ "limitations.md",
261
+ "contributing.md",
262
+ ]
263
+
264
+ for filename in doc_files:
265
+ filepath = docs_dir / filename
266
+ size = filepath.stat().st_size
267
+ assert size > 100, f"{filename} is too small ({size} bytes)"
268
+
269
+ @given(st.just(None))
270
+ def test_property_all_files_have_headings(self, _):
271
+ """
272
+ Property: All documentation files have proper markdown headings
273
+ For each documentation file, it should start with a markdown heading.
274
+ """
275
+ docs_dir = Path("docs")
276
+ doc_files = [
277
+ "index.md",
278
+ "getting-started.md",
279
+ "features.md",
280
+ "installation.md",
281
+ "api-reference.md",
282
+ "examples.md",
283
+ "limitations.md",
284
+ "contributing.md",
285
+ ]
286
+
287
+ for filename in doc_files:
288
+ filepath = docs_dir / filename
289
+ with open(filepath, "r", encoding="utf-8") as f:
290
+ content = f.read()
291
+
292
+ assert content.startswith("#"), f"{filename} should start with a markdown heading"
293
+ assert "#" in content, f"{filename} should contain markdown headings"
294
+
295
+ @given(st.just(None))
296
+ def test_property_index_has_navigation(self, _):
297
+ """
298
+ Property: Index page has navigation to all sections
299
+ The index.md file should contain links to all 7 documentation sections.
300
+ """
301
+ with open("docs/index.md", "r", encoding="utf-8") as f:
302
+ content = f.read()
303
+
304
+ required_links = [
305
+ "getting-started.md",
306
+ "features.md",
307
+ "installation.md",
308
+ "api-reference.md",
309
+ "examples.md",
310
+ "limitations.md",
311
+ "contributing.md",
312
+ ]
313
+
314
+ for link in required_links:
315
+ assert link in content, f"index.md missing link to {link}"
316
+
317
+ @given(st.just(None))
318
+ def test_property_readme_has_quick_reference(self, _):
319
+ """
320
+ Property: README has quick reference table
321
+ The main README.md should contain a quick reference table with functions.
322
+ """
323
+ with open("README.md", "r", encoding="utf-8") as f:
324
+ content = f.read()
325
+
326
+ # Check for table structure
327
+ assert "|" in content, "README missing table structure"
328
+ assert "explain_error" in content, "README missing explain_error in quick reference"
329
+ assert "safe_get" in content, "README missing safe_get in quick reference"
@@ -0,0 +1,349 @@
1
+ """
2
+ Unit tests for documentation structure and content.
3
+
4
+ Tests verify that all documentation files exist, contain required sections,
5
+ and have proper links and navigation.
6
+ """
7
+
8
+ import os
9
+ import re
10
+ from pathlib import Path
11
+
12
+
13
+ class TestDocumentationStructure:
14
+ """Test documentation file structure and existence."""
15
+
16
+ def test_all_documentation_files_exist(self):
17
+ """Test that all required documentation files exist in docs/ folder."""
18
+ docs_dir = Path("docs")
19
+ required_files = [
20
+ "index.md",
21
+ "getting-started.md",
22
+ "features.md",
23
+ "installation.md",
24
+ "api-reference.md",
25
+ "examples.md",
26
+ "limitations.md",
27
+ "contributing.md",
28
+ ]
29
+
30
+ for filename in required_files:
31
+ filepath = docs_dir / filename
32
+ assert filepath.exists(), f"Documentation file {filename} not found in docs/"
33
+ assert filepath.is_file(), f"{filename} is not a file"
34
+
35
+ def test_all_files_are_markdown(self):
36
+ """Test that all documentation files have .md extension."""
37
+ docs_dir = Path("docs")
38
+ required_files = [
39
+ "index.md",
40
+ "getting-started.md",
41
+ "features.md",
42
+ "installation.md",
43
+ "api-reference.md",
44
+ "examples.md",
45
+ "limitations.md",
46
+ "contributing.md",
47
+ ]
48
+
49
+ for filename in required_files:
50
+ assert filename.endswith(".md"), f"{filename} does not have .md extension"
51
+
52
+
53
+ class TestGettingStartedContent:
54
+ """Test Getting Started documentation content."""
55
+
56
+ def test_getting_started_contains_installation_section(self):
57
+ """Test that Getting Started contains installation instructions."""
58
+ with open("docs/getting-started.md", "r", encoding="utf-8") as f:
59
+ content = f.read()
60
+
61
+ assert "Installation" in content, "Getting Started missing Installation section"
62
+ assert "pip install" in content, "Getting Started missing pip install command"
63
+
64
+ def test_getting_started_contains_first_example(self):
65
+ """Test that Getting Started contains a first example."""
66
+ with open("docs/getting-started.md", "r", encoding="utf-8") as f:
67
+ content = f.read()
68
+
69
+ assert "example" in content.lower(), "Getting Started missing example"
70
+ assert "```python" in content, "Getting Started missing code example"
71
+
72
+ def test_getting_started_contains_links(self):
73
+ """Test that Getting Started contains links to other sections."""
74
+ with open("docs/getting-started.md", "r", encoding="utf-8") as f:
75
+ content = f.read()
76
+
77
+ assert "[Installation]" in content, "Getting Started missing link to Installation"
78
+ assert "[Examples]" in content, "Getting Started missing link to Examples"
79
+ assert "[Features]" in content, "Getting Started missing link to Features"
80
+
81
+
82
+ class TestFeaturesContent:
83
+ """Test Features documentation content."""
84
+
85
+ def test_features_contains_feature_list(self):
86
+ """Test that Features contains a list of features."""
87
+ with open("docs/features.md", "r", encoding="utf-8") as f:
88
+ content = f.read()
89
+
90
+ assert "Error Explanation" in content, "Features missing Error Explanation"
91
+ assert "Safe Utilities" in content, "Features missing Safe Utilities"
92
+ assert "Learning Tools" in content, "Features missing Learning Tools"
93
+
94
+ def test_features_contains_descriptions(self):
95
+ """Test that Features contains descriptions of each feature."""
96
+ with open("docs/features.md", "r", encoding="utf-8") as f:
97
+ content = f.read()
98
+
99
+ # Check for feature descriptions
100
+ assert "explain_error" in content, "Features missing explain_error description"
101
+ assert "safe_get" in content, "Features missing safe_get description"
102
+
103
+
104
+ class TestInstallationContent:
105
+ """Test Installation documentation content."""
106
+
107
+ def test_installation_contains_system_requirements(self):
108
+ """Test that Installation contains system requirements."""
109
+ with open("docs/installation.md", "r", encoding="utf-8") as f:
110
+ content = f.read()
111
+
112
+ assert "Python" in content, "Installation missing Python requirement"
113
+ assert "3.8" in content, "Installation missing Python version requirement"
114
+
115
+ def test_installation_contains_os_specific_instructions(self):
116
+ """Test that Installation contains instructions for different OS."""
117
+ with open("docs/installation.md", "r", encoding="utf-8") as f:
118
+ content = f.read()
119
+
120
+ assert "Linux" in content, "Installation missing Linux instructions"
121
+ assert "macOS" in content, "Installation missing macOS instructions"
122
+ assert "Windows" in content, "Installation missing Windows instructions"
123
+
124
+ def test_installation_contains_dependencies(self):
125
+ """Test that Installation mentions dependencies."""
126
+ with open("docs/installation.md", "r", encoding="utf-8") as f:
127
+ content = f.read()
128
+
129
+ assert "requests" in content, "Installation missing requests dependency"
130
+ assert "click" in content, "Installation missing click dependency"
131
+
132
+
133
+ class TestAPIReferenceContent:
134
+ """Test API Reference documentation content."""
135
+
136
+ def test_api_reference_contains_main_functions(self):
137
+ """Test that API Reference documents main functions."""
138
+ with open("docs/api-reference.md", "r", encoding="utf-8") as f:
139
+ content = f.read()
140
+
141
+ assert "explain_error" in content, "API Reference missing explain_error"
142
+ assert "safe_get" in content, "API Reference missing safe_get"
143
+ assert "explain" in content, "API Reference missing explain"
144
+
145
+ def test_api_reference_contains_parameters(self):
146
+ """Test that API Reference includes function parameters."""
147
+ with open("docs/api-reference.md", "r", encoding="utf-8") as f:
148
+ content = f.read()
149
+
150
+ assert "Parameters" in content, "API Reference missing Parameters section"
151
+ assert "Returns" in content, "API Reference missing Returns section"
152
+
153
+ def test_api_reference_contains_examples(self):
154
+ """Test that API Reference includes code examples."""
155
+ with open("docs/api-reference.md", "r", encoding="utf-8") as f:
156
+ content = f.read()
157
+
158
+ assert "```python" in content, "API Reference missing code examples"
159
+ assert "Example" in content, "API Reference missing Example section"
160
+
161
+
162
+ class TestExamplesContent:
163
+ """Test Examples documentation content."""
164
+
165
+ def test_examples_contains_code_examples(self):
166
+ """Test that Examples contains code examples."""
167
+ with open("docs/examples.md", "r", encoding="utf-8") as f:
168
+ content = f.read()
169
+
170
+ assert "```python" in content, "Examples missing Python code"
171
+ assert "Example" in content, "Examples missing Example sections"
172
+
173
+ def test_examples_contains_multiple_examples(self):
174
+ """Test that Examples contains multiple examples."""
175
+ with open("docs/examples.md", "r", encoding="utf-8") as f:
176
+ content = f.read()
177
+
178
+ # Count example sections
179
+ example_count = content.count("## Example")
180
+ assert example_count >= 3, f"Examples should have at least 3 examples, found {example_count}"
181
+
182
+
183
+ class TestLimitationsContent:
184
+ """Test Limitations documentation content."""
185
+
186
+ def test_limitations_contains_limitation_list(self):
187
+ """Test that Limitations contains a list of limitations."""
188
+ with open("docs/limitations.md", "r", encoding="utf-8") as f:
189
+ content = f.read()
190
+
191
+ assert "SyntaxError" in content, "Limitations missing SyntaxError limitation"
192
+ assert "OOP" in content or "Object-Oriented" in content, "Limitations missing OOP limitation"
193
+
194
+ def test_limitations_contains_explanations(self):
195
+ """Test that Limitations explains each limitation."""
196
+ with open("docs/limitations.md", "r", encoding="utf-8") as f:
197
+ content = f.read()
198
+
199
+ assert "Problem" in content or "The Problem" in content, "Limitations missing problem descriptions"
200
+ assert "Workaround" in content, "Limitations missing workarounds"
201
+
202
+
203
+ class TestContributingContent:
204
+ """Test Contributing documentation content."""
205
+
206
+ def test_contributing_contains_contribution_types(self):
207
+ """Test that Contributing explains how to contribute."""
208
+ with open("docs/contributing.md", "r", encoding="utf-8") as f:
209
+ content = f.read()
210
+
211
+ assert "Report bugs" in content or "bug" in content.lower(), "Contributing missing bug reporting info"
212
+ assert "code" in content.lower(), "Contributing missing code contribution info"
213
+
214
+ def test_contributing_contains_development_setup(self):
215
+ """Test that Contributing includes development setup instructions."""
216
+ with open("docs/contributing.md", "r", encoding="utf-8") as f:
217
+ content = f.read()
218
+
219
+ assert "Fork" in content, "Contributing missing fork instructions"
220
+ assert "git clone" in content, "Contributing missing clone instructions"
221
+ assert "pip install" in content, "Contributing missing installation instructions"
222
+
223
+ def test_contributing_contains_testing_info(self):
224
+ """Test that Contributing includes testing information."""
225
+ with open("docs/contributing.md", "r", encoding="utf-8") as f:
226
+ content = f.read()
227
+
228
+ assert "pytest" in content, "Contributing missing pytest information"
229
+ assert "test" in content.lower(), "Contributing missing testing information"
230
+
231
+
232
+ class TestDocumentationLinks:
233
+ """Test that documentation links are properly formatted."""
234
+
235
+ def test_index_contains_navigation_links(self):
236
+ """Test that index.md contains links to all sections."""
237
+ with open("docs/index.md", "r", encoding="utf-8") as f:
238
+ content = f.read()
239
+
240
+ required_links = [
241
+ "getting-started.md",
242
+ "features.md",
243
+ "installation.md",
244
+ "api-reference.md",
245
+ "examples.md",
246
+ "limitations.md",
247
+ "contributing.md",
248
+ ]
249
+
250
+ for link in required_links:
251
+ assert link in content, f"index.md missing link to {link}"
252
+
253
+ def test_all_files_have_return_link(self):
254
+ """Test that all documentation files have a link back to index."""
255
+ docs_dir = Path("docs")
256
+ doc_files = [
257
+ "getting-started.md",
258
+ "features.md",
259
+ "installation.md",
260
+ "api-reference.md",
261
+ "examples.md",
262
+ "limitations.md",
263
+ "contributing.md",
264
+ ]
265
+
266
+ for filename in doc_files:
267
+ with open(docs_dir / filename, "r", encoding="utf-8") as f:
268
+ content = f.read()
269
+
270
+ # Check for link back to index or documentation
271
+ has_return_link = (
272
+ "index.md" in content or
273
+ "Documentation Index" in content or
274
+ "Return to" in content
275
+ )
276
+ assert has_return_link, f"{filename} missing return link to index"
277
+
278
+
279
+ class TestREADMEContent:
280
+ """Test main README.md content."""
281
+
282
+ def test_readme_contains_brief_description(self):
283
+ """Test that README contains a brief project description."""
284
+ with open("README.md", "r", encoding="utf-8") as f:
285
+ content = f.read()
286
+
287
+ # Check for project description
288
+ assert "Fishertools" in content or "fishertools" in content, "README missing project name"
289
+ assert "Python" in content, "README missing Python mention"
290
+
291
+ def test_readme_contains_quick_install(self):
292
+ """Test that README contains quick installation instructions."""
293
+ with open("README.md", "r", encoding="utf-8") as f:
294
+ content = f.read()
295
+
296
+ assert "pip install" in content, "README missing pip install command"
297
+
298
+ def test_readme_contains_documentation_links(self):
299
+ """Test that README contains links to documentation sections."""
300
+ with open("README.md", "r", encoding="utf-8") as f:
301
+ content = f.read()
302
+
303
+ # Check for links to docs
304
+ assert "docs/" in content or "documentation" in content.lower(), "README missing links to documentation"
305
+
306
+
307
+ class TestDocumentationConsistency:
308
+ """Test consistency across documentation files."""
309
+
310
+ def test_all_files_have_proper_headings(self):
311
+ """Test that all documentation files have proper markdown headings."""
312
+ docs_dir = Path("docs")
313
+ doc_files = [
314
+ "index.md",
315
+ "getting-started.md",
316
+ "features.md",
317
+ "installation.md",
318
+ "api-reference.md",
319
+ "examples.md",
320
+ "limitations.md",
321
+ "contributing.md",
322
+ ]
323
+
324
+ for filename in doc_files:
325
+ with open(docs_dir / filename, "r", encoding="utf-8") as f:
326
+ content = f.read()
327
+
328
+ # Check for at least one heading
329
+ assert "#" in content, f"{filename} missing markdown headings"
330
+ assert content.startswith("#"), f"{filename} should start with a heading"
331
+
332
+ def test_all_files_are_not_empty(self):
333
+ """Test that all documentation files have content."""
334
+ docs_dir = Path("docs")
335
+ doc_files = [
336
+ "index.md",
337
+ "getting-started.md",
338
+ "features.md",
339
+ "installation.md",
340
+ "api-reference.md",
341
+ "examples.md",
342
+ "limitations.md",
343
+ "contributing.md",
344
+ ]
345
+
346
+ for filename in doc_files:
347
+ filepath = docs_dir / filename
348
+ size = filepath.stat().st_size
349
+ assert size > 100, f"{filename} is too small (likely empty or minimal)"