Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,21 @@ jobs:
matrix:
os: [ubuntu-latest]
python-version: ["3.11", "3.12", "3.13", "3.14"]
sphinx-version: ["~=7.0", "~=8.0"]
sphinx-version: ["~=7.0", "~=8.0", "~=9.0"]
extras: ["testing"]
include:
- os: windows-latest
python-version: "3.11"
sphinx-version: "~=7.0"
extras: "testing"
- os: windows-latest
python-version: "3.14"
python-version: "3.13"
sphinx-version: "~=8.0"
extras: "testing"
- os: windows-latest
python-version: "3.14"
sphinx-version: "~=9.0"
extras: "testing"

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

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ classifiers = [
]
keywords = ["sphinx", "extension", "material design", "web components"]
requires-python = ">=3.11"
dependencies = ["sphinx>=7,<9"]
dependencies = ["sphinx>=7,<10"]

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

[project.optional-dependencies]
code-style = ["pre-commit>=3,<4"]
rtd = ["myst-parser>=3,<5"]
rtd = ["myst-parser>=4,<6"]
testing = [
"myst-parser>=3,<5",
"myst-parser>=4,<6",
"pytest~=8.3",
"pytest-cov",
"pytest-regressions",
Expand Down
38 changes: 38 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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
Expand Down Expand Up @@ -88,3 +90,39 @@ def _create_project(
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)
Comment thread
chrisjsewell marked this conversation as resolved.
# 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
Comment on lines +98 to +128
Copy link
Copy Markdown

@amotl amotl Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also use that in downstream projects. Maybe relocate to sphinx_design_elements.testing, to be able to ship it with the package?

32 changes: 22 additions & 10 deletions tests/test_snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ def write_assets(src_path: Path):
ids=[path.name[: -len(path.suffix)] for path in SNIPPETS_GLOB_RST],
)
def test_snippets_rst(
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
sphinx_builder: Callable[..., SphinxBuilder],
path: Path,
file_regression,
normalize_doctree_xml,
):
"""Test snippets written in RestructuredText (before post-transforms)."""
builder = sphinx_builder(conf_kwargs={"extensions": ["sphinx_design"]})
Expand All @@ -44,7 +47,7 @@ def test_snippets_rst(
doctree = builder.get_doctree("index", post_transforms=False)
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
file_regression.check(
doctree.pformat(),
normalize_doctree_xml(doctree.pformat()),
basename=f"snippet_pre_{path.name[: -len(path.suffix)]}",
extension=".xml",
encoding="utf8",
Expand All @@ -58,7 +61,10 @@ def test_snippets_rst(
)
@pytest.mark.skipif(not MYST_INSTALLED, reason="myst-parser not installed")
def test_snippets_myst(
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
sphinx_builder: Callable[..., SphinxBuilder],
path: Path,
file_regression,
normalize_doctree_xml,
):
"""Test snippets written in MyST Markdown (before post-transforms)."""
builder = sphinx_builder()
Expand All @@ -69,7 +75,7 @@ def test_snippets_myst(
doctree = builder.get_doctree("index", post_transforms=False)
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
file_regression.check(
doctree.pformat(),
normalize_doctree_xml(doctree.pformat()),
basename=f"snippet_pre_{path.name[: -len(path.suffix)]}",
extension=".xml",
encoding="utf8",
Expand All @@ -82,7 +88,10 @@ def test_snippets_myst(
ids=[path.name[: -len(path.suffix)] for path in SNIPPETS_GLOB_RST],
)
def test_snippets_rst_post(
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
sphinx_builder: Callable[..., SphinxBuilder],
path: Path,
file_regression,
normalize_doctree_xml,
):
"""Test snippets written in RestructuredText (after HTML post-transforms)."""
builder = sphinx_builder(conf_kwargs={"extensions": ["sphinx_design"]})
Expand All @@ -93,7 +102,7 @@ def test_snippets_rst_post(
doctree = builder.get_doctree("index", post_transforms=True)
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
file_regression.check(
doctree.pformat(),
normalize_doctree_xml(doctree.pformat()),
basename=f"snippet_post_{path.name[: -len(path.suffix)]}",
extension=".xml",
encoding="utf8",
Expand All @@ -107,7 +116,10 @@ def test_snippets_rst_post(
)
@pytest.mark.skipif(not MYST_INSTALLED, reason="myst-parser not installed")
def test_snippets_myst_post(
sphinx_builder: Callable[..., SphinxBuilder], path: Path, file_regression
sphinx_builder: Callable[..., SphinxBuilder],
path: Path,
file_regression,
normalize_doctree_xml,
):
"""Test snippets written in MyST Markdown (after HTML post-transforms)."""
builder = sphinx_builder()
Expand All @@ -118,7 +130,7 @@ def test_snippets_myst_post(
doctree = builder.get_doctree("index", post_transforms=True)
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
file_regression.check(
doctree.pformat(),
normalize_doctree_xml(doctree.pformat()),
basename=f"snippet_post_{path.name[: -len(path.suffix)]}",
extension=".xml",
encoding="utf8",
Expand Down Expand Up @@ -164,7 +176,7 @@ def test_sd_hide_title_myst(

@pytest.mark.skipif(not MYST_INSTALLED, reason="myst-parser not installed")
def test_sd_custom_directives(
sphinx_builder: Callable[..., SphinxBuilder], file_regression
sphinx_builder: Callable[..., SphinxBuilder], file_regression, normalize_doctree_xml
):
"""Test that the defaults are used."""
builder = sphinx_builder(
Expand All @@ -188,7 +200,7 @@ def test_sd_custom_directives(
doctree = builder.get_doctree("index", post_transforms=False)
doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1
file_regression.check(
doctree.pformat(),
normalize_doctree_xml(doctree.pformat()),
basename="sd_custom_directives",
extension=".xml",
encoding="utf8",
Expand Down