"""Tests for the static website builder."""
from __future__ import annotations
import logging
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from build_website import (
BuildState,
PageInfo,
WebsiteConfig,
_disambiguate_url,
build_website,
collect_folder_markdown,
collect_pages,
derive_page_title,
heading_to_anchor,
is_excluded_dir,
is_excluded_top_level_markdown,
relative_link,
render_markdown,
replace_mermaid_blocks,
rewrite_links,
source_to_site_url,
)
# =============================================================================
# Fixtures
# =============================================================================
@pytest.fixture
def site_root(tmp_path: Path) -> Path:
"""Create a minimal repo-like tree the builder can render."""
(tmp_path / "README.md").write_text(
"\n'
"
' in out
assert "flowchart LR" in out
assert "```mermaid" not in out
def test_escapes_html(self) -> None:
md = "```mermaid\nA --> B\n```\n"
out = replace_mermaid_blocks(md)
assert "<C>" in out
# =============================================================================
# render_markdown
# =============================================================================
class TestRenderMarkdown:
def test_heading_gets_github_anchor(self) -> None:
html_content = render_markdown("# Hello World\n\nBody")
assert 'id="hello-world"' in html_content
def test_duplicate_headings_get_suffix(self) -> None:
html_content = render_markdown("# Hi\n\n# Hi\n")
assert 'id="hi"' in html_content
assert 'id="hi-1"' in html_content
# =============================================================================
# rewrite_links
# =============================================================================
class TestRewriteLinks:
def _state(self) -> BuildState:
state = BuildState()
state.source_to_url = {
"README.md": "index.html",
"01-slash-commands/README.md": "01-slash-commands/index.html",
}
return state
def _config(self, root: Path) -> WebsiteConfig:
return WebsiteConfig(
root_path=root,
output_path=root / "out",
repo_url="https://github.com/example/repo",
branch="main",
)
def test_internal_markdown_link_rewritten(
self, tmp_path: Path, logger: logging.Logger
) -> None:
(tmp_path / "README.md").write_text("# Home")
(tmp_path / "01-slash-commands").mkdir()
(tmp_path / "01-slash-commands" / "README.md").write_text("# Slash")
page = PageInfo(
source=tmp_path / "README.md",
rel_source="README.md",
output_url="index.html",
title="Home",
section="Introduction",
is_section_index=True,
)
html_in = 'go'
out = rewrite_links(
html_in, page, self._state(), self._config(tmp_path), logger
)
assert "01-slash-commands/index.html" in out
assert ".md" not in out
def test_anchor_preserved(self, tmp_path: Path, logger: logging.Logger) -> None:
(tmp_path / "README.md").write_text("# Home")
(tmp_path / "01-slash-commands").mkdir()
(tmp_path / "01-slash-commands" / "README.md").write_text("# Slash")
page = PageInfo(
source=tmp_path / "README.md",
rel_source="README.md",
output_url="index.html",
title="Home",
section="Introduction",
is_section_index=True,
)
html_in = 'go'
out = rewrite_links(
html_in, page, self._state(), self._config(tmp_path), logger
)
assert "#run" in out
def test_non_markdown_link_uses_github_blob(
self, tmp_path: Path, logger: logging.Logger
) -> None:
(tmp_path / "scripts").mkdir()
(tmp_path / "scripts" / "build.sh").write_text("#!/bin/bash")
(tmp_path / "README.md").write_text("# Home")
page = PageInfo(
source=tmp_path / "README.md",
rel_source="README.md",
output_url="index.html",
title="Home",
section="Introduction",
is_section_index=True,
)
html_in = 'script'
out = rewrite_links(
html_in, page, self._state(), self._config(tmp_path), logger
)
assert "github.com/example/repo/blob/main/scripts/build.sh" in out
assert 'target="_blank"' in out
def test_repo_directory_link_uses_github_tree(
self, tmp_path: Path, logger: logging.Logger
) -> None:
(tmp_path / "scripts").mkdir()
(tmp_path / "README.md").write_text("# Home")
page = PageInfo(
source=tmp_path / "README.md",
rel_source="README.md",
output_url="index.html",
title="Home",
section="Introduction",
is_section_index=True,
)
html_in = 'scripts'
out = rewrite_links(
html_in, page, self._state(), self._config(tmp_path), logger
)
assert "github.com/example/repo/tree/main/scripts" in out
assert "github.com/example/repo/blob/main/scripts" not in out
def test_repo_root_link_uses_github_tree(
self, tmp_path: Path, logger: logging.Logger
) -> None:
(tmp_path / "README.md").write_text("# Home")
page = PageInfo(
source=tmp_path / "README.md",
rel_source="README.md",
output_url="index.html",
title="Home",
section="Introduction",
is_section_index=True,
)
html_in = 'repo root'
out = rewrite_links(
html_in, page, self._state(), self._config(tmp_path), logger
)
assert "github.com/example/repo/tree/main" in out
assert "github.com/example/repo/blob/main/." not in out
def test_external_link_left_alone(
self, tmp_path: Path, logger: logging.Logger
) -> None:
page = PageInfo(
source=tmp_path / "README.md",
rel_source="README.md",
output_url="index.html",
title="Home",
section="Introduction",
is_section_index=True,
)
(tmp_path / "README.md").write_text("# Home")
html_in = 'site'
out = rewrite_links(
html_in, page, self._state(), self._config(tmp_path), logger
)
assert 'href="https://anthropic.com"' in out
# =============================================================================
# Full build smoke test
# =============================================================================
class TestBuildWebsite:
def test_smoke_build(self, site_root: Path, logger: logging.Logger) -> None:
out_dir = site_root / "site"
config = WebsiteConfig(
root_path=site_root,
output_path=out_dir,
repo_url="https://github.com/example/repo",
branch="main",
)
build_website(config, logger, skip_vendor=True)
# Index page rendered
index = out_dir / "index.html"
assert index.exists()
index_html = index.read_text(encoding="utf-8")
assert "Home Page" in index_html
assert "01-slash-commands/index.html" in index_html
assert "CONTRIBUTING.html" in index_html
# Non-markdown link rewritten to GitHub
assert "github.com/example/repo/blob/main/scripts/build.sh" in index_html
# inside rewritten to point at assets/
assert 'srcset="assets/resources/logos/' in index_html
# Additional top-level docs rendered
assert (out_dir / "CONTRIBUTING.html").exists()
assert not (out_dir / "CLAUDE.html").exists()
assert not (out_dir / "update-plan-2026-05-02.html").exists()
# Folder index rendered
sc_index = out_dir / "01-slash-commands" / "index.html"
assert sc_index.exists()
sc_html = sc_index.read_text(encoding="utf-8")
# Mermaid block became a block
assert '' in sc_html
# Sibling markdown link rewritten
assert "example.html" in sc_html
# Example page rendered
example_page = out_dir / "01-slash-commands" / "example.html"
assert example_page.exists()
example_html = example_page.read_text(encoding="utf-8")
# Back-link to README rewrites to index.html
assert "index.html" in example_html
# Stylesheet copied into assets
assert (out_dir / "assets" / "site.css").exists()
# Logo copied into assets
assert (out_dir / "assets" / "resources" / "logos" / "logo.svg").exists()
# Template no longer references third-party CDNs.
for hostile in (
"cdn.tailwindcss.com",
"cdn.jsdelivr.net",
"fonts.googleapis.com",
):
assert (
hostile not in index_html
), f"Built HTML still references {hostile} — CDN should be self-hosted"
# =============================================================================
# vendor_assets module smoke test
# =============================================================================
class TestVendorAssets:
def test_module_exports(self) -> None:
"""vendor_assets exposes the API build_website depends on."""
import vendor_assets
for attr in (
"build_tailwind_css",
"fetch_mermaid",
"fetch_fonts",
"write_vendor_manifest",
"ensure_tailwind_binary",
"TAILWIND_VERSION",
"MERMAID_VERSION",
):
assert hasattr(vendor_assets, attr), f"missing {attr}"
def test_detect_tailwind_asset_name(self) -> None:
"""Platform detection returns one of the known asset names."""
from vendor_assets import _detect_tailwind_asset_name
known = {
"tailwindcss-macos-arm64",
"tailwindcss-macos-x64",
"tailwindcss-linux-arm64",
"tailwindcss-linux-armv7",
"tailwindcss-linux-x64",
"tailwindcss-windows-x64.exe",
}
assert _detect_tailwind_asset_name() in known
def test_download_rejects_non_http_scheme(self, tmp_path: Path) -> None:
"""The _download helper refuses file:/ftp:/etc. — defense-in-depth."""
from vendor_assets import _download
with pytest.raises(ValueError, match="non-HTTP URL"):
_download("file:///etc/passwd", tmp_path / "out.bin")