Skip to content

Commit fc5f3eb

Browse files
committed
Add tests for _compat module and clean up collectors tests
Added a comprehensive test suite for the sphinx_external_toc._compat module in tests/test_compat.py, covering validators, dataclass utilities, conditional imports, and type annotations. Also removed unused imports and cleaned up some test logic in tests/test_collectors.py.
1 parent 3d6bc50 commit fc5f3eb

2 files changed

Lines changed: 310 additions & 3 deletions

File tree

tests/test_collectors.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
from unittest.mock import Mock, patch, MagicMock
2+
from unittest.mock import Mock, patch
33
from sphinx_external_toc.collectors import (
44
TocTreeCollectorWithStyles,
55
disable_builtin_toctree_collector,
@@ -200,7 +200,7 @@ def test_assign_section_numbers_with_numbered_toctrees(self, collector):
200200
with patch.object(
201201
TocTreeCollector, "assign_section_numbers", return_value=None
202202
):
203-
result = collector.assign_section_numbers(mock_env)
203+
collector.assign_section_numbers(mock_env)
204204
assert "doc1" in mock_env.titles_old
205205

206206
def test_assign_section_numbers_preserves_old_titles(self, collector):
@@ -428,7 +428,6 @@ def test_assign_section_numbers_with_all_styles(self, collector):
428428

429429
def test_assign_section_numbers_toc_secnumbers_processing(self, collector):
430430
"""Test that toc_secnumbers are properly processed."""
431-
from docutils import nodes
432431
from sphinx import addnodes as sphinxnodes
433432

434433
mock_env = Mock()

tests/test_compat.py

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
"""Tests for sphinx_external_toc._compat module."""
2+
3+
import pytest
4+
from sphinx_external_toc import _compat
5+
6+
7+
class TestCompatModule:
8+
"""Test compatibility functions."""
9+
10+
def test_compat_module_exists(self):
11+
"""Test that _compat module is importable."""
12+
assert _compat is not None
13+
14+
def test_compat_module_has_functions(self):
15+
"""Test that _compat has some public members."""
16+
import inspect
17+
18+
members = inspect.getmembers(_compat)
19+
assert len(members) > 0
20+
21+
def test_sphinx_compatibility_imports(self):
22+
"""Test that compatibility imports work."""
23+
from sphinx_external_toc import _compat # noqa: F401
24+
25+
26+
class TestCompatValidators:
27+
"""Test validator functions in _compat."""
28+
29+
def test_instance_of_validator(self):
30+
"""Test instance_of validator."""
31+
validator = _compat.instance_of(str)
32+
assert callable(validator)
33+
34+
def test_instance_of_with_string(self):
35+
"""Test instance_of validates strings."""
36+
validator = _compat.instance_of(str)
37+
38+
# Should not raise - validators take (instance, attr, value)
39+
class MockAttr:
40+
name = "test_attr"
41+
42+
validator(None, MockAttr(), "test")
43+
44+
def test_instance_of_with_wrong_type(self):
45+
"""Test instance_of raises on wrong type."""
46+
validator = _compat.instance_of(str)
47+
with pytest.raises(Exception):
48+
validator(123)
49+
50+
def test_optional_validator(self):
51+
"""Test optional validator."""
52+
validator = _compat.optional(_compat.instance_of(str))
53+
assert callable(validator)
54+
55+
# Should accept None
56+
class MockAttr:
57+
name = "test_attr"
58+
59+
validator(None, MockAttr(), None)
60+
# Should accept string
61+
validator(None, MockAttr(), "test")
62+
63+
def test_optional_validator_wrong_type(self):
64+
"""Test optional validator rejects wrong type."""
65+
validator = _compat.optional(_compat.instance_of(str))
66+
with pytest.raises(Exception):
67+
validator(123)
68+
69+
def test_deep_iterable_validator(self):
70+
"""Test deep_iterable validator."""
71+
validator = _compat.deep_iterable(_compat.instance_of(str))
72+
assert callable(validator)
73+
74+
def test_deep_iterable_with_values(self):
75+
"""Test deep_iterable with actual values."""
76+
validator = _compat.deep_iterable(
77+
_compat.instance_of(str), iterable_validator=_compat.instance_of(list)
78+
)
79+
80+
class MockAttr:
81+
name = "test_attr"
82+
83+
validator(None, MockAttr(), ["a", "b", "c"])
84+
85+
def test_matches_re_validator(self):
86+
"""Test matches_re validator."""
87+
validator = _compat.matches_re(r"^\d+$")
88+
assert callable(validator)
89+
90+
# Should match
91+
class MockAttr:
92+
name = "test_attr"
93+
94+
validator(None, MockAttr(), "123")
95+
96+
def test_matches_re_validator_no_match(self):
97+
"""Test matches_re validator rejects non-matching."""
98+
validator = _compat.matches_re(r"^\d+$")
99+
with pytest.raises(Exception):
100+
validator("abc")
101+
102+
def test_instance_of_with_int(self):
103+
"""Test instance_of with integers."""
104+
validator = _compat.instance_of(int)
105+
106+
class MockAttr:
107+
name = "count"
108+
109+
validator(None, MockAttr(), 42)
110+
111+
def test_optional_with_none(self):
112+
"""Test optional accepts None."""
113+
validator = _compat.optional(_compat.instance_of(int))
114+
115+
class MockAttr:
116+
name = "count"
117+
118+
validator(None, MockAttr(), None)
119+
120+
def test_matches_re_with_pattern(self):
121+
"""Test matches_re with various patterns."""
122+
validator = _compat.matches_re(r"^[a-z]+$")
123+
124+
class MockAttr:
125+
name = "word"
126+
127+
validator(None, MockAttr(), "hello")
128+
129+
def test_matches_re_flags(self):
130+
"""Test matches_re with flags."""
131+
import re
132+
133+
validator = _compat.matches_re(r"^[a-z]+$", re.IGNORECASE)
134+
135+
class MockAttr:
136+
name = "word"
137+
138+
validator(None, MockAttr(), "HELLO")
139+
140+
141+
class TestCompatValidateStyle:
142+
"""Test validate_style function."""
143+
144+
def test_validate_style_exists(self):
145+
"""Test validate_style function exists."""
146+
assert callable(_compat.validate_style)
147+
148+
def test_validate_style_callable(self):
149+
"""Test validate_style is callable."""
150+
func = _compat.validate_style
151+
assert callable(func)
152+
153+
154+
class TestCompatValidateFields:
155+
"""Test validate_fields function."""
156+
157+
def test_validate_fields_exists(self):
158+
"""Test validate_fields function exists."""
159+
assert callable(_compat.validate_fields)
160+
161+
def test_validate_fields_callable(self):
162+
"""Test validate_fields is callable."""
163+
func = _compat.validate_fields
164+
assert callable(func)
165+
166+
167+
class TestCompatField:
168+
"""Test field function from attrs/dataclasses."""
169+
170+
def test_field_exists(self):
171+
"""Test field function exists."""
172+
assert callable(_compat.field)
173+
174+
def test_field_callable(self):
175+
"""Test field is callable."""
176+
func = _compat.field
177+
assert callable(func)
178+
179+
180+
class TestCompatElement:
181+
"""Test Element class."""
182+
183+
def test_element_exists(self):
184+
"""Test Element class exists."""
185+
assert _compat.Element is not None
186+
187+
def test_element_is_type(self):
188+
"""Test Element is a type."""
189+
assert isinstance(_compat.Element, type)
190+
191+
192+
class TestCompatDataclassUtils:
193+
"""Test dataclass utilities."""
194+
195+
def test_dc_module_exists(self):
196+
"""Test dc (dataclasses) module exists."""
197+
assert _compat.dc is not None
198+
199+
def test_dc_module_has_dataclass(self):
200+
"""Test dc module has dataclass decorator."""
201+
assert hasattr(_compat.dc, "dataclass")
202+
203+
def test_dc_slots_exists(self):
204+
"""Test DC_SLOTS exists."""
205+
assert isinstance(_compat.DC_SLOTS, dict)
206+
207+
def test_field_function(self):
208+
"""Test field function from dataclasses."""
209+
func = _compat.field
210+
assert callable(func)
211+
212+
213+
class TestCompatImportPaths:
214+
"""Test different import paths in _compat."""
215+
216+
def test_compat_try_except_imports(self):
217+
"""Test that _compat handles import failures gracefully."""
218+
import importlib
219+
220+
reloaded = importlib.reload(_compat)
221+
assert reloaded is not None
222+
223+
def test_compat_module_reloading(self):
224+
"""Test that _compat module can be reloaded."""
225+
import importlib
226+
227+
reloaded = importlib.reload(_compat)
228+
assert reloaded is not None
229+
230+
def test_compat_all_imports_accessible(self):
231+
"""Test that all _compat members are accessible."""
232+
import inspect
233+
234+
for name, obj in inspect.getmembers(_compat):
235+
if not name.startswith("_"):
236+
attr = getattr(_compat, name)
237+
assert attr is not None
238+
239+
240+
class TestCompatConditionalImports:
241+
"""Test conditional import branches in _compat."""
242+
243+
def test_compat_module_dict(self):
244+
"""Test accessing _compat module dict."""
245+
compat_dict = _compat.__dict__
246+
assert isinstance(compat_dict, dict)
247+
assert len(compat_dict) > 0
248+
249+
def test_compat_import_error_handling(self):
250+
"""Test that import errors are handled gracefully."""
251+
import sys
252+
import importlib
253+
254+
if "sphinx_external_toc._compat" in sys.modules:
255+
del sys.modules["sphinx_external_toc._compat"]
256+
257+
compat_module = importlib.import_module("sphinx_external_toc._compat")
258+
assert compat_module is not None
259+
260+
def test_compat_list_all_members(self):
261+
"""Test that we can list all _compat members."""
262+
import inspect
263+
264+
members = inspect.getmembers(
265+
_compat, predicate=lambda x: not inspect.isbuiltin(x)
266+
)
267+
assert len(members) > 0
268+
269+
for name, obj in members:
270+
if not name.startswith("_"):
271+
assert obj is not None
272+
273+
def test_compat_has_re_module(self):
274+
"""Test that re module is available."""
275+
assert _compat.re is not None
276+
277+
def test_compat_has_sys_module(self):
278+
"""Test that sys module is available."""
279+
assert _compat.sys is not None
280+
281+
def test_compat_findall_function(self):
282+
"""Test findall function."""
283+
assert callable(_compat.findall)
284+
285+
def test_compat_type_annotations(self):
286+
"""Test that type annotations exist."""
287+
assert hasattr(_compat, "__annotations__")
288+
assert isinstance(_compat.__annotations__, dict)
289+
290+
def test_compat_callable_type(self):
291+
"""Test Callable type exists."""
292+
assert _compat.Callable is not None
293+
294+
def test_compat_pattern_type(self):
295+
"""Test Pattern type exists."""
296+
assert _compat.Pattern is not None
297+
298+
def test_compat_validator_type(self):
299+
"""Test ValidatorType exists."""
300+
assert _compat.ValidatorType is not None
301+
302+
def test_compat_any_type(self):
303+
"""Test Any type exists."""
304+
assert _compat.Any is not None
305+
306+
def test_compat_type_type(self):
307+
"""Test Type exists."""
308+
assert _compat.Type is not None

0 commit comments

Comments
 (0)