mews.page/pelican-mews/tests/test_mews.py

116 lines
4.3 KiB
Python
Raw Normal View History

2026-10-11 08:09:18 +03:00
"""End-to-end tests: Pelican builds small fixture sites with the plugin."""
from __future__ import annotations
from importlib.resources import files
from pathlib import Path
import shutil
import subprocess
import zipfile
from conftest import FIXTURES, build_case, normalize
import pytest
from pelican.plugins.mews.mews import CSS_NAME
from pelican.plugins.mews.subset import MewsSubsetError, parse_allowlist
PACKAGE = "pelican.plugins.mews"
STRICT_OVERRIDES = {"MEWS_STRICT": True}
@pytest.mark.parametrize("case", ["basic", "strip", "cssdir"])
def test_written_page_matches_expected(tmp_path, case):
"""The written page matches the fixture, whatever was stripped."""
overrides = {"MEWS_CSS_DIR": "style"} if case == "cssdir" else {}
output = build_case(case, tmp_path, **overrides)
page = (output / "page.xhtml").read_bytes()
expected = (FIXTURES / case / "expected.xhtml").read_bytes()
assert normalize(page) == normalize(expected)
@pytest.mark.parametrize("case", ["basic", "strip", "cssdir"])
def test_css_is_copied(tmp_path, case):
"""The bundled stylesheet lands under the configured directory."""
overrides = {"MEWS_CSS_DIR": "style"} if case == "cssdir" else {}
output = build_case(case, tmp_path, **overrides)
css_dir = "style" if case == "cssdir" else "css"
bundled = files(PACKAGE).joinpath("static", CSS_NAME).read_bytes()
assert (output / css_dir / CSS_NAME).read_bytes() == bundled
def test_template_global_points_at_the_css(tmp_path):
"""Themes see MEWS_CSS as the relative stylesheet path."""
output = build_case("basic", tmp_path)
page = (output / "page.xhtml").read_text(encoding="utf-8")
assert 'href="css/mews-0.1.css"' in page
def test_lenient_mode_logs_removals(tmp_path, caplog):
"""Default mode strips and logs what it removed."""
caplog.set_level("WARNING", logger=f"{PACKAGE}.mews")
build_case("strip", tmp_path)
assert "<span> is not in the Mews subset" in caplog.text
assert "em class is not in the Mews subset" in caplog.text
def test_strict_mode_fails_the_build(tmp_path):
"""MEWS_STRICT turns disallowed content into a build failure."""
with pytest.raises(MewsSubsetError, match="span"):
build_case("strict", tmp_path, **STRICT_OVERRIDES)
def test_parallel_output_folder(tmp_path):
"""MEWS_OUTPUT_PATH keeps the normal output free of Mews files.
The processed page and the stylesheet move to the parallel folder,
keeping the subpath; the directory the move emptied is pruned.
"""
parallel = tmp_path / "mews"
output = build_case(
"basic",
tmp_path,
MEWS_OUTPUT_PATH=str(parallel),
PAGE_SAVE_AS="posts/{slug}.xhtml",
PAGE_URL="posts/{slug}.xhtml",
)
expected = (FIXTURES / "basic" / "expected.xhtml").read_bytes()
page = (parallel / "posts" / "page.xhtml").read_bytes()
assert normalize(page) == normalize(expected)
assert (parallel / "css" / CSS_NAME).exists()
assert not (output / "posts").exists()
assert not (output / "css").exists()
def test_allowlist_is_read_from_the_dtd():
"""The subset comes from the DTD, not from code."""
dtd = files(PACKAGE).joinpath("dtd", "mews.dtd").read_text(encoding="utf-8")
allowlist = parse_allowlist(dtd)
assert allowlist["p"] == {"id", "title", "xml:lang"}
assert allowlist["html"] == {"dir", "id", "lang", "title", "xml:lang", "xmlns"}
assert "href" in allowlist["a"]
@pytest.mark.skipif(shutil.which("uv") is None, reason="uv is not installed")
def test_wheel_bundles_plugin_data(tmp_path):
"""The wheel carries the CSS, templates and DTD.
The namespace directories ship without __init__.py.
"""
root = Path(__file__).resolve().parents[1]
subprocess.run(
["uv", "build", "--wheel", "-o", str(tmp_path), str(root)],
check=True,
capture_output=True,
)
(wheel,) = tmp_path.glob("pelican_mews-*.whl")
names = set(zipfile.ZipFile(wheel).namelist())
expected = {
"pelican/plugins/mews/static/mews-0.1.css",
"pelican/plugins/mews/templates/mews/head.html",
"pelican/plugins/mews/dtd/mews.dtd",
"pelican/plugins/mews/py.typed",
}
assert expected <= names
assert "pelican/__init__.py" not in names
assert "pelican/plugins/__init__.py" not in names