diff --git a/python/tests/unit/extensions/test_glightbox.py b/python/tests/unit/extensions/test_glightbox.py
index 6d67ab8..378c442 100644
--- a/python/tests/unit/extensions/test_glightbox.py
+++ b/python/tests/unit/extensions/test_glightbox.py
@@ -30,6 +30,8 @@ import pytest
from tests.unit.extensions.conftest import soup
if TYPE_CHECKING:
+ from collections.abc import Iterator
+
from markdown import Markdown
# ---------------------------------------------------------------------------
@@ -48,6 +50,24 @@ def _glightbox(**kwargs: object) -> dict[str, Any]:
}
+class _TrackingBlocks(list[str]):
+ """Track indexes read from stashed blocks."""
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.visited: list[int] = []
+
+ def __iter__(self) -> Iterator[str]:
+ for index, value in enumerate(super().__iter__()):
+ self.visited.append(index)
+ yield value
+
+ def __getitem__(self, index: Any) -> Any:
+ if isinstance(index, int):
+ self.visited.append(index)
+ return super().__getitem__(index)
+
+
# ---------------------------------------------------------------------------
# Basic wrapping
# ---------------------------------------------------------------------------
@@ -388,3 +408,34 @@ class TestPostprocessor:
def test_raw_html_skip_class_not_wrapped(self, md: Markdown) -> None:
html = soup(md.convert('
'))
assert html.select_one("a.glightbox") is None
+
+ @pytest.mark.parametrize(
+ "md",
+ [pytest.param(_glightbox(), id="default")],
+ indirect=["md"],
+ )
+ def test_toc_does_not_rescan_stash(
+ self, md: Markdown, monkeypatch: pytest.MonkeyPatch
+ ) -> None:
+ """TOC rendering does not cause processed blocks to be scanned again."""
+ blocks = _TrackingBlocks()
+ md.htmlStash.rawHtmlBlocks = blocks
+ processor = md.postprocessors["glightbox"]
+ original = processor.run
+ visits: list[list[int]] = []
+
+ def run(text: str) -> str:
+ start = len(blocks.visited)
+ text = original(text)
+ visits.append(blocks.visited[start:])
+ return text
+
+ monkeypatch.setattr(processor, "run", run)
+ html = soup(
+ md.convert('# One\n\n## Two\n\n## Three\n\n
')
+ )
+
+ assert len(visits) > 1
+ assert visits[0] == list(range(len(blocks)))
+ assert all(not visited for visited in visits[1:])
+ assert len(html.select("a.glightbox")) == 1
diff --git a/python/tests/unit/extensions/test_links.py b/python/tests/unit/extensions/test_links.py
index f70b4f9..939aa24 100644
--- a/python/tests/unit/extensions/test_links.py
+++ b/python/tests/unit/extensions/test_links.py
@@ -23,16 +23,56 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
import pytest
from markdown import Markdown
from zensical.extensions.links import (
LinksExtension,
+ LinksPostprocessor,
_is_relative,
_md_path_to_html,
_rewrite_url,
)
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+ from typing import Any
+
+
+class _TrackingBlocks(list[str]):
+ """Track indexes read from stashed blocks."""
+
+ def __init__(self, values: list[str]) -> None:
+ super().__init__(values)
+ self.visited: list[int] = []
+
+ def __iter__(self) -> Iterator[str]:
+ for index, value in enumerate(super().__iter__()):
+ self.visited.append(index)
+ yield value
+
+ def __getitem__(self, index: Any) -> Any:
+ if isinstance(index, int):
+ self.visited.append(index)
+ return super().__getitem__(index)
+
+
+class _TrackingPostprocessor(LinksPostprocessor):
+ """Record which stash indexes each invocation reads."""
+
+ def __init__(self, md: Markdown, blocks: _TrackingBlocks) -> None:
+ super().__init__(md, "guide/page.md", True)
+ self._blocks = blocks
+ self.visits: list[list[int]] = []
+
+ def run(self, text: str) -> str:
+ start = len(self._blocks.visited)
+ text = super().run(text)
+ self.visits.append(self._blocks.visited[start:])
+ return text
+
@pytest.mark.parametrize(
("path", "directory_urls", "expected"),
@@ -109,3 +149,21 @@ def test_rewrites_links_in_stashed_raw_html() -> None:
assert md.convert('