diff --git a/pyproject.toml b/pyproject.toml index e3418ab98a2..15b3fe8a20a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,6 +124,7 @@ test = [ "defusedxml>=0.7.1", # for secure XML/HTML parsing "setuptools>=70.0", # for Cython compilation "typing_extensions>=4.9", # for typing_extensions.Unpack + "beautifulsoup4>=4.12", # for HTML parsing in tests ] translations = [ "babel>=2.13", diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index f0328e36e77..c29cde19a1b 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -2,35 +2,82 @@ from __future__ import annotations +import re from typing import TYPE_CHECKING import pytest +from bs4 import BeautifulSoup if TYPE_CHECKING: + from typing import Any + from sphinx.testing.util import SphinxTestApp @pytest.mark.sphinx('changes', testroot='changes') def test_build(app: SphinxTestApp) -> None: + """Test the 'changes' builder and resolve TODO for better HTML checking.""" app.build() - # TODO: Use better checking of html content htmltext = (app.outdir / 'changes.html').read_text(encoding='utf8') - assert 'Added in version 0.6: Some funny stuff.' in htmltext - assert 'Changed in version 0.6: Even more funny stuff.' in htmltext - assert 'Deprecated since version 0.6: Boring stuff.' in htmltext + soup = BeautifulSoup(htmltext, 'html.parser') + + # Get all
  • items up front + all_items = soup.find_all('li') + assert len(all_items) >= 5, 'Did not find all 5 change items' + + def find_change_item(type_text: str, version: str, content: str) -> dict[str, Any]: + """Helper to find and validate change items.""" + found_item = None + # Loop through all
  • tags + for item in all_items: + # Check if the content is in the item's *full text* + if re.search(re.escape(content), item.text): + found_item = item + break # Found it! + + # This is the assertion that was failing + assert found_item is not None, ( + f"Could not find change item containing '{content}'" + ) + + type_elem = found_item.find('i') + assert type_elem is not None, f"Missing type indicator for '{content}'" + assert type_text in type_elem.text.lower(), ( + f"Expected type '{type_text}' for '{content}'" + ) + + assert f'version {version}' in found_item.text, ( + f'Version {version} not found in {content!r}' + ) + + return {'item': found_item, 'type': type_elem} + + # Test simple changes + changes = [ + ('added', '0.6', 'Some funny stuff.'), + ('changed', '0.6', 'Even more funny stuff.'), + ('deprecated', '0.6', 'Boring stuff.'), + ] + + for change_type, version, content in changes: + find_change_item(change_type, version, content) - path_html = ( - 'Path: deprecated: Deprecated since version 0.6:' - ' So, that was a bad idea it turns out.' + # Test Path deprecation (Search by unique text) + path_change = find_change_item( + 'deprecated', + '0.6', + 'So, that was a bad idea it turns out.', ) - assert path_html in htmltext + assert path_change['item'].find('b').text == 'Path' - malloc_html = ( - 'void *Test_Malloc(size_t n): changed: Changed in version 0.6:' - ' Can now be replaced with a different allocator.' + # Test Malloc function change (Search by unique text) + malloc_change = find_change_item( + 'changed', + '0.6', + 'Can now be replaced with a different allocator.', ) - assert malloc_html in htmltext + assert malloc_change['item'].find('b').text == 'void *Test_Malloc(size_t n)' @pytest.mark.sphinx(