Skip to content

Commit a5e3fc8

Browse files
committed
Add navbar, meta, and heading a11y tests
1 parent c07bbf2 commit a5e3fc8

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

tests/test_gdg_rendered.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,104 @@ def test_no_broken_heading_attributes(pkg_name: str):
18241824
)
18251825

18261826

1827+
# ═══════════════════════════════════════════════════════════════════════════════
1828+
# R4: Navbar Rendering (nav)
1829+
# ═══════════════════════════════════════════════════════════════════════════════
1830+
1831+
1832+
@requires_bs4
1833+
@pytest.mark.parametrize("pkg_name", _RENDERED_PACKAGES)
1834+
def test_navbar_renders_with_links(pkg_name: str):
1835+
"""The navbar renders with at least one navigation link."""
1836+
index = _site_dir(pkg_name) / "index.html"
1837+
if not index.exists():
1838+
pytest.skip("No index.html")
1839+
1840+
soup = _load_html(index)
1841+
nav = soup.select_one("nav.navbar")
1842+
if nav is None:
1843+
pytest.fail(f"{pkg_name}: no <nav class='navbar'> element found")
1844+
1845+
links = nav.select("a[href]")
1846+
assert links, f"{pkg_name}: navbar has no links"
1847+
1848+
# The brand/title link should be present
1849+
brand = nav.select_one(".navbar-brand, .navbar-title")
1850+
assert brand, f"{pkg_name}: navbar missing brand/title element"
1851+
1852+
1853+
# ═══════════════════════════════════════════════════════════════════════════════
1854+
# R4: Page Metadata / OG Tags (meta)
1855+
# ═══════════════════════════════════════════════════════════════════════════════
1856+
1857+
1858+
@requires_bs4
1859+
@pytest.mark.parametrize("pkg_name", _RENDERED_PACKAGES)
1860+
def test_page_has_meta_description(pkg_name: str):
1861+
"""The landing page has a <meta name='description'> tag."""
1862+
index = _site_dir(pkg_name) / "index.html"
1863+
if not index.exists():
1864+
pytest.skip("No index.html")
1865+
1866+
soup = _load_html(index)
1867+
meta = soup.select_one("meta[name='description']")
1868+
assert meta, f"{pkg_name}: missing <meta name='description'>"
1869+
content = meta.get("content", "").strip()
1870+
assert content, f"{pkg_name}: meta description is empty"
1871+
1872+
1873+
@requires_bs4
1874+
@pytest.mark.parametrize("pkg_name", _RENDERED_PACKAGES)
1875+
def test_page_has_og_title(pkg_name: str):
1876+
"""The landing page has an og:title meta tag."""
1877+
index = _site_dir(pkg_name) / "index.html"
1878+
if not index.exists():
1879+
pytest.skip("No index.html")
1880+
1881+
soup = _load_html(index)
1882+
og = soup.select_one("meta[property='og:title']")
1883+
assert og, f"{pkg_name}: missing <meta property='og:title'>"
1884+
content = og.get("content", "").strip()
1885+
assert content, f"{pkg_name}: og:title is empty"
1886+
1887+
1888+
# ═══════════════════════════════════════════════════════════════════════════════
1889+
# R4: Accessibility (a11y)
1890+
# ═══════════════════════════════════════════════════════════════════════════════
1891+
1892+
1893+
@requires_bs4
1894+
@pytest.mark.parametrize("pkg_name", _RENDERED_PACKAGES)
1895+
def test_heading_hierarchy_no_skips(pkg_name: str):
1896+
"""Heading levels don't skip (e.g., h2 → h4 without h3) in main content.
1897+
1898+
Allows the initial jump from h1 to any level (Quarto sidebars/layouts
1899+
commonly inject headings at varying levels before the first section).
1900+
Only checks forward-skips: going from hN to hM where M > N+1.
1901+
"""
1902+
index = _site_dir(pkg_name) / "index.html"
1903+
if not index.exists():
1904+
pytest.skip("No index.html")
1905+
1906+
soup = _load_html(index)
1907+
main = soup.select_one("main, #quarto-document-content, main.content")
1908+
if main is None:
1909+
pytest.skip("No main content area")
1910+
1911+
headings = main.select("h1, h2, h3, h4, h5, h6")
1912+
if len(headings) < 3:
1913+
pytest.skip("Too few headings to check hierarchy")
1914+
1915+
levels = [int(h.name[1]) for h in headings]
1916+
# Skip the first heading pair (h1 → h3 is common in Quarto layouts)
1917+
for i in range(2, len(levels)):
1918+
if levels[i] > levels[i - 1] + 1:
1919+
pytest.fail(
1920+
f"{pkg_name}: heading skip from h{levels[i - 1]} to h{levels[i]} "
1921+
f"(headings {i} to {i + 1})"
1922+
)
1923+
1924+
18271925
# ═══════════════════════════════════════════════════════════════════════════════
18281926
# R4: Directive Stripping
18291927
# ═══════════════════════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)