diff --git a/pyproject.toml b/pyproject.toml index edf8592e0..94ea79e51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -305,7 +305,6 @@ module = [ "tests.test_quickstart", "tests.test_search", # tests/test_builders - "tests.test_builders.test_build_epub", "tests.test_builders.test_build_gettext", "tests.test_builders.test_build_latex", "tests.test_builders.test_build_texinfo", diff --git a/tests/test_builders/test_build_epub.py b/tests/test_builders/test_build_epub.py index c025b2f8f..fc5547d7e 100644 --- a/tests/test_builders/test_build_epub.py +++ b/tests/test_builders/test_build_epub.py @@ -7,14 +7,21 @@ import subprocess import xml.etree.ElementTree as ET from pathlib import Path from subprocess import CalledProcessError +from typing import TYPE_CHECKING import pytest from sphinx.builders.epub3 import _XML_NAME_PATTERN +if TYPE_CHECKING: + from collections.abc import Iterator + from typing import Self + + from sphinx.testing.util import SphinxTestApp + # check given command is runnable -def runnable(command): +def runnable(command: list[str]) -> bool: try: subprocess.run(command, capture_output=True, check=True) return True @@ -34,35 +41,41 @@ class EPUBElementTree: 'epub': 'http://www.idpf.org/2007/ops', } - def __init__(self, tree): + def __init__(self, tree: ET.Element) -> None: self.tree = tree @classmethod - def fromstring(cls, string): + def fromstring(cls, string: str | bytes) -> Self: tree = ET.fromstring(string) # NoQA: S314 # using known data in tests return cls(tree) - def find(self, match): + def find(self, match: str) -> Self: ret = self.tree.find(match, namespaces=self.namespaces) - if ret is not None: - return self.__class__(ret) - else: - return ret + assert ret is not None + return self.__class__(ret) - def findall(self, match): + def findall(self, match: str) -> list[Self]: ret = self.tree.findall(match, namespaces=self.namespaces) return [self.__class__(e) for e in ret] - def __getattr__(self, name): - return getattr(self.tree, name) + @property + def text(self) -> str | None: + return self.tree.text - def __iter__(self): + @property + def attrib(self) -> dict[str, str]: + return self.tree.attrib + + def get(self, key: str) -> str | None: + return self.tree.get(key) + + def __iter__(self) -> Iterator[Self]: for child in self.tree: yield self.__class__(child) @pytest.mark.sphinx('epub', testroot='basic') -def test_build_epub(app): +def test_build_epub(app: SphinxTestApp) -> None: app.build(force_all=True) assert (app.outdir / 'mimetype').read_text( encoding='utf8' @@ -169,11 +182,13 @@ def test_build_epub(app): # nav.xhtml / nav navlist = nav.find('./xhtml:body/xhtml:nav') - toc = navlist.findall('./xhtml:ol/xhtml:li') + tocs = navlist.findall('./xhtml:ol/xhtml:li') assert navlist.find('./xhtml:h1').text == 'Table of Contents' - assert len(toc) == 1 - assert toc[0].find('./xhtml:a').get('href') == 'index.xhtml' - assert toc[0].find('./xhtml:a').text == 'The basic Sphinx documentation for testing' + assert len(tocs) == 1 + assert tocs[0].find('./xhtml:a').get('href') == 'index.xhtml' + assert ( + tocs[0].find('./xhtml:a').text == 'The basic Sphinx documentation for testing' + ) @pytest.mark.sphinx( @@ -181,7 +196,7 @@ def test_build_epub(app): testroot='footnotes', confoverrides={'epub_cover': ('_images/rimg.png', None)}, ) -def test_epub_cover(app): +def test_epub_cover(app: SphinxTestApp) -> None: app.build() # content.opf / metadata @@ -197,7 +212,7 @@ def test_epub_cover(app): @pytest.mark.sphinx('epub', testroot='toctree') -def test_nested_toc(app): +def test_nested_toc(app: SphinxTestApp) -> None: app.build() # toc.ncx @@ -205,14 +220,16 @@ def test_nested_toc(app): assert toc.find('./ncx:docTitle/ncx:text').text == 'Project name not set' # toc.ncx / navPoint - def navinfo(elem: EPUBElementTree): + def toc_navpoint_navinfo( + elem: EPUBElementTree, + ) -> tuple[str | None, str | None, str | None, str | None]: label = elem.find('./ncx:navLabel/ncx:text') content = elem.find('./ncx:content') return elem.get('id'), elem.get('playOrder'), content.get('src'), label.text navpoints = toc.findall('./ncx:navMap/ncx:navPoint') assert len(navpoints) == 4 - assert navinfo(navpoints[0]) == ( + assert toc_navpoint_navinfo(navpoints[0]) == ( 'navPoint1', '1', 'index.xhtml', @@ -221,43 +238,63 @@ def test_nested_toc(app): assert navpoints[0].findall('./ncx:navPoint') == [] # toc.ncx / nested navPoints - assert navinfo(navpoints[1]) == ('navPoint2', '2', 'foo.xhtml', 'foo') + assert toc_navpoint_navinfo(navpoints[1]) == ('navPoint2', '2', 'foo.xhtml', 'foo') navchildren = navpoints[1].findall('./ncx:navPoint') assert len(navchildren) == 4 - assert navinfo(navchildren[0]) == ('navPoint3', '2', 'foo.xhtml', 'foo') - assert navinfo(navchildren[1]) == ('navPoint4', '3', 'quux.xhtml', 'quux') - assert navinfo(navchildren[2]) == ('navPoint5', '4', 'foo.xhtml#foo-1', 'foo.1') - assert navinfo(navchildren[3]) == ('navPoint8', '6', 'foo.xhtml#foo-2', 'foo.2') + assert toc_navpoint_navinfo(navchildren[0]) == ( + 'navPoint3', + '2', + 'foo.xhtml', + 'foo', + ) + assert toc_navpoint_navinfo(navchildren[1]) == ( + 'navPoint4', + '3', + 'quux.xhtml', + 'quux', + ) + assert toc_navpoint_navinfo(navchildren[2]) == ( + 'navPoint5', + '4', + 'foo.xhtml#foo-1', + 'foo.1', + ) + assert toc_navpoint_navinfo(navchildren[3]) == ( + 'navPoint8', + '6', + 'foo.xhtml#foo-2', + 'foo.2', + ) # nav.xhtml / nav - def navinfo(elem): + def nav_nav_navinfo(elem: EPUBElementTree) -> tuple[str | None, str | None]: anchor = elem.find('./xhtml:a') return anchor.get('href'), anchor.text nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').read_bytes()) - toc = nav.findall('./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li') - assert len(toc) == 4 - assert navinfo(toc[0]) == ( + tocs = nav.findall('./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li') + assert len(tocs) == 4 + assert nav_nav_navinfo(tocs[0]) == ( 'index.xhtml', 'Welcome to Sphinx Tests’s documentation!', ) - assert toc[0].findall('./xhtml:ol') == [] + assert tocs[0].findall('./xhtml:ol') == [] # nav.xhtml / nested toc - assert navinfo(toc[1]) == ('foo.xhtml', 'foo') - tocchildren = toc[1].findall('./xhtml:ol/xhtml:li') + assert nav_nav_navinfo(tocs[1]) == ('foo.xhtml', 'foo') + tocchildren = tocs[1].findall('./xhtml:ol/xhtml:li') assert len(tocchildren) == 3 - assert navinfo(tocchildren[0]) == ('quux.xhtml', 'quux') - assert navinfo(tocchildren[1]) == ('foo.xhtml#foo-1', 'foo.1') - assert navinfo(tocchildren[2]) == ('foo.xhtml#foo-2', 'foo.2') + assert nav_nav_navinfo(tocchildren[0]) == ('quux.xhtml', 'quux') + assert nav_nav_navinfo(tocchildren[1]) == ('foo.xhtml#foo-1', 'foo.1') + assert nav_nav_navinfo(tocchildren[2]) == ('foo.xhtml#foo-2', 'foo.2') grandchild = tocchildren[1].findall('./xhtml:ol/xhtml:li') assert len(grandchild) == 1 - assert navinfo(grandchild[0]) == ('foo.xhtml#foo-1-1', 'foo.1-1') + assert nav_nav_navinfo(grandchild[0]) == ('foo.xhtml#foo-1-1', 'foo.1-1') @pytest.mark.sphinx('epub', testroot='need-escaped') -def test_escaped_toc(app): +def test_escaped_toc(app: SphinxTestApp) -> None: app.build() # toc.ncx @@ -265,14 +302,17 @@ def test_escaped_toc(app): assert toc.find('./ncx:docTitle/ncx:text').text == 'need "escaped" project' # toc.ncx / navPoint - def navinfo(elem): + def navpoint_navinfo( + elem: EPUBElementTree, + ) -> tuple[str | None, str | None, str | None, str | None]: label = elem.find('./ncx:navLabel/ncx:text') content = elem.find('./ncx:content') - return elem.get('id'), elem.get('playOrder'), content.get('src'), label.text + ret = elem.get('id'), elem.get('playOrder'), content.get('src'), label.text + return ret navpoints = toc.findall('./ncx:navMap/ncx:navPoint') assert len(navpoints) == 4 - assert navinfo(navpoints[0]) == ( + assert navpoint_navinfo(navpoints[0]) == ( 'navPoint1', '1', 'index.xhtml', @@ -281,43 +321,53 @@ def test_escaped_toc(app): assert navpoints[0].findall('./ncx:navPoint') == [] # toc.ncx / nested navPoints - assert navinfo(navpoints[1]) == ('navPoint2', '2', 'foo.xhtml', '') + assert navpoint_navinfo(navpoints[1]) == ('navPoint2', '2', 'foo.xhtml', '') navchildren = navpoints[1].findall('./ncx:navPoint') assert len(navchildren) == 4 - assert navinfo(navchildren[0]) == ('navPoint3', '2', 'foo.xhtml', '') - assert navinfo(navchildren[1]) == ('navPoint4', '3', 'quux.xhtml', 'quux') - assert navinfo(navchildren[2]) == ('navPoint5', '4', 'foo.xhtml#foo-1', 'foo “1”') - assert navinfo(navchildren[3]) == ('navPoint8', '6', 'foo.xhtml#foo-2', 'foo.2') + assert navpoint_navinfo(navchildren[0]) == ('navPoint3', '2', 'foo.xhtml', '') + assert navpoint_navinfo(navchildren[1]) == ('navPoint4', '3', 'quux.xhtml', 'quux') + assert navpoint_navinfo(navchildren[2]) == ( + 'navPoint5', + '4', + 'foo.xhtml#foo-1', + 'foo “1”', + ) + assert navpoint_navinfo(navchildren[3]) == ( + 'navPoint8', + '6', + 'foo.xhtml#foo-2', + 'foo.2', + ) # nav.xhtml / nav - def navinfo(elem): + def nav_navinfo(elem: EPUBElementTree) -> tuple[str | None, str | None]: anchor = elem.find('./xhtml:a') return anchor.get('href'), anchor.text nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').read_bytes()) - toc = nav.findall('./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li') - assert len(toc) == 4 - assert navinfo(toc[0]) == ( + tocs = nav.findall('./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li') + assert len(tocs) == 4 + assert nav_navinfo(tocs[0]) == ( 'index.xhtml', "Welcome to Sphinx Tests's documentation!", ) - assert toc[0].findall('./xhtml:ol') == [] + assert tocs[0].findall('./xhtml:ol') == [] # nav.xhtml / nested toc - assert navinfo(toc[1]) == ('foo.xhtml', '') - tocchildren = toc[1].findall('./xhtml:ol/xhtml:li') + assert nav_navinfo(tocs[1]) == ('foo.xhtml', '') + tocchildren = tocs[1].findall('./xhtml:ol/xhtml:li') assert len(tocchildren) == 3 - assert navinfo(tocchildren[0]) == ('quux.xhtml', 'quux') - assert navinfo(tocchildren[1]) == ('foo.xhtml#foo-1', 'foo “1”') - assert navinfo(tocchildren[2]) == ('foo.xhtml#foo-2', 'foo.2') + assert nav_navinfo(tocchildren[0]) == ('quux.xhtml', 'quux') + assert nav_navinfo(tocchildren[1]) == ('foo.xhtml#foo-1', 'foo “1”') + assert nav_navinfo(tocchildren[2]) == ('foo.xhtml#foo-2', 'foo.2') grandchild = tocchildren[1].findall('./xhtml:ol/xhtml:li') assert len(grandchild) == 1 - assert navinfo(grandchild[0]) == ('foo.xhtml#foo-1-1', 'foo.1-1') + assert nav_navinfo(grandchild[0]) == ('foo.xhtml#foo-1-1', 'foo.1-1') @pytest.mark.sphinx('epub', testroot='basic') -def test_epub_writing_mode(app): +def test_epub_writing_mode(app: SphinxTestApp) -> None: # horizontal (default) app.build(force_all=True) @@ -361,7 +411,7 @@ def test_epub_writing_mode(app): @pytest.mark.sphinx('epub', testroot='epub-anchor-id') -def test_epub_anchor_id(app): +def test_epub_anchor_id(app: SphinxTestApp) -> None: app.build() html = (app.outdir / 'index.xhtml').read_text(encoding='utf8') @@ -375,7 +425,7 @@ def test_epub_anchor_id(app): @pytest.mark.sphinx('epub', testroot='html_assets') -def test_epub_assets(app): +def test_epub_assets(app: SphinxTestApp) -> None: app.build(force_all=True) # epub_sytlesheets (same as html_css_files) @@ -394,7 +444,7 @@ def test_epub_assets(app): testroot='html_assets', confoverrides={'epub_css_files': ['css/epub.css']}, ) -def test_epub_css_files(app): +def test_epub_css_files(app: SphinxTestApp) -> None: app.build(force_all=True) # epub_css_files @@ -414,7 +464,7 @@ def test_epub_css_files(app): @pytest.mark.sphinx('epub', testroot='roles-download') -def test_html_download_role(app): +def test_html_download_role(app: SphinxTestApp) -> None: app.build() assert not (app.outdir / '_downloads' / 'dummy.dat').exists() @@ -436,7 +486,7 @@ def test_html_download_role(app): @pytest.mark.sphinx('epub', testroot='toctree-duplicated') -def test_duplicated_toctree_entry(app): +def test_duplicated_toctree_entry(app: SphinxTestApp) -> None: app.build(force_all=True) assert 'WARNING: duplicated ToC entry found: foo.xhtml' in app.warning.getvalue() @@ -446,7 +496,7 @@ def test_duplicated_toctree_entry(app): reason='Skipped because DO_EPUBCHECK is not set', ) @pytest.mark.sphinx('epub', testroot='root') -def test_run_epubcheck(app): +def test_run_epubcheck(app: SphinxTestApp) -> None: app.build() if not runnable(['java', '-version']): @@ -469,7 +519,7 @@ def test_run_epubcheck(app): raise AssertionError(msg) from exc -def test_xml_name_pattern_check(): +def test_xml_name_pattern_check() -> None: assert _XML_NAME_PATTERN.match('id-pub') assert _XML_NAME_PATTERN.match('webpage') assert not _XML_NAME_PATTERN.match('1bfda21') @@ -477,7 +527,7 @@ def test_xml_name_pattern_check(): @pytest.mark.usefixtures('_http_teapot') @pytest.mark.sphinx('epub', testroot='images') -def test_copy_images(app): +def test_copy_images(app: SphinxTestApp) -> None: app.build() images_dir = Path(app.outdir) / '_images'