Skip to content

Commit 6f3fdc4

Browse files
committed
⬆️ Add Sphinx 9 support
- Update sphinx dependency to allow version 9 (`sphinx>=7,<10`) - Add Sphinx 9.0 to CI test matrix (Ubuntu and Windows) - Bump myst-parser dependency to `>=4,<6` - Fix test compatibility with docutils 0.22+ boolean attribute serialization (normalizes `"1"`/`"0"` back to `"True"`/`"False"` in XML output)
1 parent d8daebf commit 6f3fdc4

File tree

4 files changed

+57
-15
lines changed

4 files changed

+57
-15
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,21 @@ jobs:
2828
matrix:
2929
os: [ubuntu-latest]
3030
python-version: ["3.11", "3.12", "3.13", "3.14"]
31-
sphinx-version: ["~=7.0", "~=8.0"]
31+
sphinx-version: ["~=7.0", "~=8.0", "~=9.0"]
3232
extras: ["testing"]
3333
include:
3434
- os: windows-latest
3535
python-version: "3.11"
3636
sphinx-version: "~=7.0"
3737
extras: "testing"
3838
- os: windows-latest
39-
python-version: "3.14"
39+
python-version: "3.13"
4040
sphinx-version: "~=8.0"
4141
extras: "testing"
42+
- os: windows-latest
43+
python-version: "3.14"
44+
sphinx-version: "~=9.0"
45+
extras: "testing"
4246

4347
runs-on: ${{ matrix.os }}
4448

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ classifiers = [
2727
]
2828
keywords = ["sphinx", "extension", "material design", "web components"]
2929
requires-python = ">=3.11"
30-
dependencies = ["sphinx>=7,<9"]
30+
dependencies = ["sphinx>=7,<10"]
3131

3232
[project.urls]
3333
Homepage = "https://github.com/executablebooks/sphinx-design"
3434
Documentation = "https://sphinx-design.readthedocs.io"
3535

3636
[project.optional-dependencies]
3737
code-style = ["pre-commit>=3,<4"]
38-
rtd = ["myst-parser>=3,<5"]
38+
rtd = ["myst-parser>=4,<6"]
3939
testing = [
40-
"myst-parser>=3,<5",
40+
"myst-parser>=4,<6",
4141
"pytest~=8.3",
4242
"pytest-cov",
4343
"pytest-regressions",

tests/conftest.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
22
from pathlib import Path
3+
import re
34
from typing import Any
45

6+
from docutils import __version_info__ as docutils_version_info
57
from docutils import nodes
68
import pytest
79
from sphinx import version_info
@@ -88,3 +90,39 @@ def _create_project(
8890
return SphinxBuilder(app, src_path)
8991

9092
yield _create_project
93+
94+
95+
DOCUTILS_0_22_PLUS = docutils_version_info >= (0, 22)
96+
97+
98+
@pytest.fixture
99+
def normalize_doctree_xml():
100+
"""Normalize docutils XML output for cross-version compatibility.
101+
102+
In docutils 0.22+, boolean attributes are serialized as "1"/"0"
103+
instead of "True"/"False". This function normalizes to the old format
104+
for consistent test fixtures.
105+
"""
106+
107+
def _normalize(text: str) -> str:
108+
if DOCUTILS_0_22_PLUS:
109+
# Normalize new format (1/0) to old format (1/0)
110+
# Only replace when it's clearly a boolean attribute value
111+
# Pattern: attribute="1" or attribute="0"
112+
attrs = [
113+
"checked",
114+
"force",
115+
"has_title",
116+
"internal",
117+
"is_div",
118+
"linenos",
119+
"opened",
120+
"refexplicit",
121+
"refwarn",
122+
"selected",
123+
]
124+
text = re.sub(rf' ({"|".join(attrs)})="1"', r' \1="True"', text)
125+
text = re.sub(rf' ({"|".join(attrs)})="0"', r' \1="False"', text)
126+
return text
127+
128+
return _normalize

tests/test_snippets.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def write_assets(src_path: Path):
3333
ids=[path.name[: -len(path.suffix)] for path in SNIPPETS_GLOB_RST],
3434
)
3535
def test_snippets_rst(
36-
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
36+
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression, normalize_doctree_xml
3737
):
3838
"""Test snippets written in RestructuredText (before post-transforms)."""
3939
builder = sphinx_builder(conf_kwargs={"extensions": ["sphinx_design"]})
@@ -44,7 +44,7 @@ def test_snippets_rst(
4444
doctree = builder.get_doctree("index", post_transforms=False)
4545
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
4646
file_regression.check(
47-
doctree.pformat(),
47+
normalize_doctree_xml(doctree.pformat()),
4848
basename=f"snippet_pre_{path.name[: -len(path.suffix)]}",
4949
extension=".xml",
5050
encoding="utf8",
@@ -58,7 +58,7 @@ def test_snippets_rst(
5858
)
5959
@pytest.mark.skipif(not MYST_INSTALLED, reason="myst-parser not installed")
6060
def test_snippets_myst(
61-
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
61+
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression, normalize_doctree_xml
6262
):
6363
"""Test snippets written in MyST Markdown (before post-transforms)."""
6464
builder = sphinx_builder()
@@ -69,7 +69,7 @@ def test_snippets_myst(
6969
doctree = builder.get_doctree("index", post_transforms=False)
7070
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
7171
file_regression.check(
72-
doctree.pformat(),
72+
normalize_doctree_xml(doctree.pformat()),
7373
basename=f"snippet_pre_{path.name[: -len(path.suffix)]}",
7474
extension=".xml",
7575
encoding="utf8",
@@ -82,7 +82,7 @@ def test_snippets_myst(
8282
ids=[path.name[: -len(path.suffix)] for path in SNIPPETS_GLOB_RST],
8383
)
8484
def test_snippets_rst_post(
85-
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
85+
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression, normalize_doctree_xml
8686
):
8787
"""Test snippets written in RestructuredText (after HTML post-transforms)."""
8888
builder = sphinx_builder(conf_kwargs={"extensions": ["sphinx_design"]})
@@ -93,7 +93,7 @@ def test_snippets_rst_post(
9393
doctree = builder.get_doctree("index", post_transforms=True)
9494
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
9595
file_regression.check(
96-
doctree.pformat(),
96+
normalize_doctree_xml(doctree.pformat()),
9797
basename=f"snippet_post_{path.name[: -len(path.suffix)]}",
9898
extension=".xml",
9999
encoding="utf8",
@@ -107,7 +107,7 @@ def test_snippets_rst_post(
107107
)
108108
@pytest.mark.skipif(not MYST_INSTALLED, reason="myst-parser not installed")
109109
def test_snippets_myst_post(
110-
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
110+
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression, normalize_doctree_xml
111111
):
112112
"""Test snippets written in MyST Markdown (after HTML post-transforms)."""
113113
builder = sphinx_builder()
@@ -118,7 +118,7 @@ def test_snippets_myst_post(
118118
doctree = builder.get_doctree("index", post_transforms=True)
119119
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
120120
file_regression.check(
121-
doctree.pformat(),
121+
normalize_doctree_xml(doctree.pformat()),
122122
basename=f"snippet_post_{path.name[: -len(path.suffix)]}",
123123
extension=".xml",
124124
encoding="utf8",
@@ -164,7 +164,7 @@ def test_sd_hide_title_myst(
164164

165165
@pytest.mark.skipif(not MYST_INSTALLED, reason="myst-parser not installed")
166166
def test_sd_custom_directives(
167-
sphinx_builder: Callable[..., SphinxBuilder], file_regression
167+
sphinx_builder: Callable[..., SphinxBuilder], file_regression, normalize_doctree_xml
168168
):
169169
"""Test that the defaults are used."""
170170
builder = sphinx_builder(
@@ -188,7 +188,7 @@ def test_sd_custom_directives(
188188
doctree = builder.get_doctree("index", post_transforms=False)
189189
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
190190
file_regression.check(
191-
doctree.pformat(),
191+
normalize_doctree_xml(doctree.pformat()),
192192
basename="sd_custom_directives",
193193
extension=".xml",
194194
encoding="utf8",

0 commit comments

Comments
 (0)