{name}
"
"Saved result: 42 cells.
"
"def count():\n return 42\n\nprint(count())\n42
"
@@ -643,7 +643,9 @@ def rendered_discovery_files(
"# CyteBase\n\n```{autoclass} cytebase.Catalog\n:members:\n```\n"
)
(html / "api" / "cytebase.html").write_text(
- 'CyteBase API reference"
+ ''
+ ''
"Catalog(bucket=None, token=None, *, version=None)"
)
@@ -677,6 +679,95 @@ def rendered_discovery_files(
return html, api_text, base_url
+@pytest.mark.parametrize(
+ ("route", "title", "expected"),
+ [
+ ("/", "CyteBase - CyteBase", "CyteBase documentation"),
+ ("/quickstart/", "Quick start - CyteBase", "Quick start - CyteBase"),
+ (
+ "/api/index.html",
+ "CyteBase Python API reference — CyteBase API reference",
+ "CyteBase Python API reference",
+ ),
+ (
+ "/api/cytebase.html",
+ "CyteBase API reference — CyteBase API reference",
+ "CyteBase Python API",
+ ),
+ ],
+)
+def test_page_metadata_preserves_authored_content_and_completes_link_previews(
+ route: str, title: str, expected: str
+) -> None:
+ pytest.importorskip("jupytext")
+ pytest.importorskip("nbformat")
+ pytest.importorskip("yaml")
+ from docs import build_docs
+
+ class Metadata(HTMLParser):
+ def __init__(self) -> None:
+ super().__init__()
+ self.tags: list[dict[str, str | None]] = []
+
+ def handle_starttag(
+ self, tag: str, attrs: list[tuple[str, str | None]]
+ ) -> None:
+ if tag == "meta":
+ self.tags.append(dict(attrs))
+
+ body = "Keep this heading
"
+ source = (
+ f"{title}"
+ ''
+ ''
+ ''
+ ''
+ f"{body}"
+ )
+ canonical = build_docs.PUBLIC_URL + route
+
+ actual = build_docs.update_page_metadata(source, canonical)
+
+ head = actual.split("")[0]
+ parser = Metadata()
+ parser.feed(head)
+ keys = [tag.get("property", tag.get("name")) for tag in parser.tags]
+ assert len(keys) == len(set(keys))
+ metadata = dict(zip(keys, (tag["content"] for tag in parser.tags), strict=True))
+ assert re.findall(r"(.*?)", head) == [expected]
+ assert metadata["description"] == 'Read RNA & ATAC "together".'
+ assert metadata["og:description"] == metadata["description"]
+ assert metadata["og:title"] == expected
+ assert metadata["og:type"] == "website"
+ assert metadata["og:url"] == canonical
+ assert (
+ metadata["og:image"]
+ == "https://docs.nygen.io/assets/brand/logo/cytebase-dark.png"
+ )
+ assert metadata["og:image:type"] == "image/png"
+ assert metadata["og:image:width"] == metadata["og:image:height"] == "512"
+ assert metadata["og:image:alt"] == "CyteBase logo"
+ assert metadata["twitter:card"] == "summary"
+ assert metadata["twitter:image"] == metadata["og:image"]
+ assert metadata["twitter:image:alt"] == metadata["og:image:alt"]
+ assert metadata["robots"] == "noindex,follow"
+ assert actual.endswith(body + "")
+ assert build_docs.update_page_metadata(actual, canonical) == actual
+
+
+def test_page_metadata_rejects_missing_authored_description() -> None:
+ pytest.importorskip("jupytext")
+ pytest.importorskip("nbformat")
+ pytest.importorskip("yaml")
+ from docs import build_docs
+
+ with pytest.raises(ValueError, match="Expected a title and description"):
+ build_docs.update_page_metadata(
+ "Guide",
+ build_docs.PUBLIC_URL + "/guide/",
+ )
+
+
def _discovery_links(page: Path) -> list[dict[str, str | None]]:
class LinkParser(HTMLParser):
def __init__(self) -> None:
@@ -1145,3 +1236,160 @@ def test_text_export_preserves_saved_markdown_instead_of_object_repr(
exported = (html / "quickstart" / "index.txt").read_text()
assert markdown in exported
assert "" not in exported
+
+
+@pytest.mark.parametrize("base_url", ["", "/CyteBase"])
+def test_api_pages_join_the_staged_guide_navigation(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch, base_url: str
+) -> None:
+ pytest.importorskip("jupytext")
+ pytest.importorskip("nbformat")
+ yaml = pytest.importorskip("yaml")
+ from docs import build_docs
+
+ api = tmp_path / "docs" / "build" / "api"
+ api.mkdir(parents=True)
+ (api / "index.html").write_text(
+ '"
+ )
+ site = tmp_path / "site"
+ site.mkdir()
+ config = {
+ "project": {
+ "toc": [
+ {"file": "index.md"},
+ {
+ "title": "Reference",
+ "children": [
+ {"url": f"{base_url}/api/index.html", "title": "API reference"}
+ ],
+ },
+ ]
+ }
+ }
+ (site / "myst.yml").write_text(yaml.safe_dump(config))
+ monkeypatch.setattr(build_docs, "DOCS", api.parents[1])
+ monkeypatch.setenv("BASE_URL", base_url)
+
+ build_docs.include_api_navigation(site)
+
+ toc = yaml.safe_load((site / "myst.yml").read_text())["project"]["toc"]
+ assert toc[0] == {"file": "index.md"}
+ assert toc[1]["children"][0]["children"] == [
+ {
+ "title": "Catalog API reference",
+ "url": f"{base_url}/api/cytebase.html",
+ "open_in_same_tab": True,
+ },
+ {
+ "title": "Import & export",
+ "url": f"{base_url}/api/import_export.html",
+ "open_in_same_tab": True,
+ },
+ ]
+
+
+@pytest.mark.parametrize("base_url", ["", "/CyteBase"])
+def test_api_pages_keep_the_guide_header_navigation_and_existing_content(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch, base_url: str
+) -> None:
+ pytest.importorskip("jupytext")
+ pytest.importorskip("nbformat")
+ pytest.importorskip("yaml")
+ from docs import build_docs
+
+ monkeypatch.setenv("BASE_URL", base_url)
+ html = tmp_path / "html"
+ api = html / "api"
+ api.mkdir(parents=True)
+ header = (
+ ''
+ )
+ navigation = (
+ '"
+ )
+ (html / "index.html").write_text(
+ f"{header}{navigation}"
+ )
+ article = 'Catalog
Method'
+ for name in ("index.html", "cytebase.html", "search.html", "genindex.html"):
+ (api / name).write_text(
+ ''
+ '{article}"
+ )
+ (api / "objects.inv").write_bytes(b"existing Sphinx inventory")
+ static = api / "_static"
+ static.mkdir()
+ (static / "webpack-macros.html").write_text("{% macro assets() %}{% endmacro %}")
+
+ build_docs.integrate_api_navigation(html)
+
+ for page in api.glob("*.html"):
+ contents = page.read_text()
+ copied_header = build_docs.html_fragment(contents, "myst-top-nav-bar")
+ copied_navigation = build_docs.html_fragment(
+ contents, "myst-primary-sidebar-toc"
+ )
+ assert 'class="myst-search-bar search-button__button"' in copied_header
+ assert (
+ 'class="myst-top-nav-menu-button sidebar-toggle primary-toggle"'
+ in copied_header
+ )
+ assert "myst-search-bar-disabled" not in copied_header
+ assert 'aria-label="Search documentation"' in copied_header
+ assert 'aria-controls="pst-search-dialog"' in copied_header
+ assert "aria-expanded" not in copied_header
+ assert "data-state" not in copied_header
+ assert "