File: conftest.py

package info (click to toggle)
sphinx-design 0.7.0-1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 10,900 kB
  • sloc: python: 2,123; xml: 918; javascript: 56; sh: 8; makefile: 3
file content (128 lines) | stat: -rw-r--r-- 4,094 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import os
from pathlib import Path
import re
from typing import Any

from docutils import __version_info__ as docutils_version_info
from docutils import nodes
import pytest
from sphinx import version_info
from sphinx.testing.util import SphinxTestApp

from sphinx_design._compat import findall

pytest_plugins = "sphinx.testing.fixtures"

if version_info >= (7, 2):
    # see https://github.com/sphinx-doc/sphinx/pull/11526
    from pathlib import Path as sphinx_path  # noqa: N813
else:
    from sphinx.testing.path import path as sphinx_path  # type: ignore[no-redef]


class SphinxBuilder:
    def __init__(self, app: SphinxTestApp, src_path: Path):
        self.app = app
        self._src_path = src_path

    @property
    def src_path(self) -> Path:
        return self._src_path

    @property
    def out_path(self) -> Path:
        return Path(self.app.outdir)

    def build(self, assert_pass=True):
        self.app.build()
        if assert_pass:
            assert self.warnings == "", self.status
        return self

    @property
    def status(self):
        return self.app._status.getvalue()

    @property
    def warnings(self):
        return self.app._warning.getvalue()

    def get_doctree(
        self, docname: str, post_transforms: bool = False
    ) -> nodes.document:
        doctree = self.app.env.get_doctree(docname)
        if post_transforms:
            self.app.env.apply_post_transforms(doctree, docname)
        # make source path consistent for test comparisons
        for node in findall(doctree)(include_self=True):
            if not (hasattr(node, "get") and node.get("source")):
                continue
            node["source"] = Path(node["source"]).relative_to(self.src_path).as_posix()
            if node["source"].endswith(".rst"):
                node["source"] = node["source"][:-4]
            elif node["source"].endswith(".md"):
                node["source"] = node["source"][:-3]
        # remove mathjax classes added by myst parser
        if doctree.children and isinstance(doctree.children[0], nodes.section):
            doctree.children[0]["classes"] = []
        return doctree


@pytest.fixture()
def sphinx_builder(tmp_path: Path, make_app, monkeypatch):
    def _create_project(
        buildername: str = "html", conf_kwargs: dict[str, Any] | None = None
    ):
        src_path = tmp_path / "srcdir"
        src_path.mkdir()
        conf_kwargs = conf_kwargs or {
            "extensions": ["myst_parser", "sphinx_design"],
            "myst_enable_extensions": ["colon_fence"],
        }
        content = "\n".join(
            [f"{key} = {value!r}" for key, value in conf_kwargs.items()]
        )
        src_path.joinpath("conf.py").write_text(content, encoding="utf8")
        app = make_app(
            srcdir=sphinx_path(os.path.abspath(str(src_path))),  # noqa: PTH100
            buildername=buildername,
        )
        return SphinxBuilder(app, src_path)

    yield _create_project


DOCUTILS_0_22_PLUS = docutils_version_info >= (0, 22)


@pytest.fixture
def normalize_doctree_xml():
    """Normalize docutils XML output for cross-version compatibility.

    In docutils 0.22+, boolean attributes are serialized as "1"/"0"
    instead of "True"/"False". This function normalizes to the old format
    for consistent test fixtures.
    """

    def _normalize(text: str) -> str:
        if DOCUTILS_0_22_PLUS:
            # Normalize new format (1/0) to old format (1/0)
            # Only replace when it's clearly a boolean attribute value
            # Pattern: attribute="1" or attribute="0"
            attrs = [
                "checked",
                "force",
                "has_title",
                "internal",
                "is_div",
                "linenos",
                "opened",
                "refexplicit",
                "refwarn",
                "selected",
            ]
            text = re.sub(rf' ({"|".join(attrs)})="1"', r' \1="True"', text)
            text = re.sub(rf' ({"|".join(attrs)})="0"', r' \1="False"', text)
        return text

    return _normalize