"""The guides and concept pages: the middle of the site, and the half of the promise tests/test_site_build.py does not cover. That file is about the generator — a renamed heading stops the build, a dead link stops the build. This one is about what a reader actually gets once it has run: the 1a Harbour furniture around each slice (sidebar, on-this-page, prev/next, "Edit this page"), and the three markdown constructs the sources really contain rendering as themselves rather than as escaped text. The rule underneath all of it: no page body is authored twice. A page authors its title and one lede sentence; everything else on it was cut out of AGENTS.md or README.md by site/pages.json. python3 -m unittest discover -s tests """ import json import re import shutil import tempfile import unittest from pathlib import Path from tests.test_site_build import (BUILDER, HAS_MARKDOWN_IT, REPO, SITE, ScratchCase, needs_renderer, run_build) # The two the layout owes a reader at the foot of every article. FLOW_LINK = re.compile( r'class="flow-link flow-(prev|next)" href="([^"]+)"') HEADING_ID = re.compile(r'

(.*?)

', re.S) # Both rails are written twice — the column the design draws, and the # folded
strip that replaces it below the breakpoint. So these # assert every rendering, rather than assuming there is one. CONTENTS_BLOCK = re.compile( r'
(.*?)
|', re.S) def contents_lists(html): """The anchors of each rendering of "On this page", in order. The sidebar's own menu-panel carries no toc-links and drops out.""" found = [TOC_LINK.findall(a or b) for a, b in CONTENTS_BLOCK.findall(html)] return [links for links in found if links] class BuiltSite(unittest.TestCase): """The real manifest, built once into a scratch directory.""" @classmethod def setUpClass(cls): if not HAS_MARKDOWN_IT: raise unittest.SkipTest("markdown-it-py is not installed") cls.out = Path(tempfile.mkdtemp(prefix="bench-pages-")).resolve() cls.result = run_build(REPO, cls.out) if cls.result.returncode != 0: # not assert: must survive python -O raise RuntimeError( f"site/build.py failed:\n{cls.result.stdout}" f"{cls.result.stderr}") cls.manifest = json.loads( (SITE / "pages.json").read_text(encoding="utf-8")) cls.flow = BUILDER.flow(cls.manifest) @classmethod def tearDownClass(cls): if hasattr(cls, "out"): shutil.rmtree(cls.out, ignore_errors=True) def page(self, route: str) -> str: return BUILDER.target_for(self.out, route).read_text("utf-8") def articles(self) -> list: """Every entry on the flow that renders in the 1a article layout — the guides and the concepts. The reference section rides a layout of its own; tests/test_site_reference.py is its half of this file.""" return [page for page in self.flow if page["layout"] == "article"] class EveryRouteRendersInTheArticleLayout(BuiltSite): """Acceptance: the routes render in 1a, and every body on them is a slice rather than something a person typed into the site.""" def test_the_seven_concepts_and_the_install_guide_are_all_there(self): """Named one by one rather than counted: a route quietly dropped from the manifest is exactly the failure this catches.""" routes = {page["path"] for page in self.articles()} for route in ("/guides/install/", "/concepts/stages/", "/concepts/task-files/", "/concepts/claiming-a-card/", "/concepts/agents-on-the-board/", "/concepts/pull-requests/", "/concepts/team-mode/", "/concepts/three-layer-law/", "/concepts/adapters/"): self.assertIn(route, routes) self.assertTrue(BUILDER.target_for(self.out, route).is_file(), f"{route} produced no page") def test_each_one_is_the_three_column_layout(self): for entry in self.articles(): html = self.page(entry["path"]) self.assertEqual("article", entry["layout"], entry["path"]) for furniture in ('class="page-article"', 'class="side"', 'class="prose"', 'class="gutter"', 'class="crumbs"'): self.assertIn(furniture, html, f'{entry["path"]} is missing {furniture}') def test_no_body_is_authored_twice(self): """Every page on the flow names a source file, and either the heading to cut from or the generator that builds the body out of it. The lede is the single exception, and it is one sentence in the manifest — not a body.""" for entry in self.flow: self.assertTrue(entry.get("source"), f'{entry["path"]} has no source') self.assertTrue(entry.get("from") or entry.get("generate"), f'{entry["path"]} has neither a from heading ' f"nor a generator") def test_the_lede_is_present_and_is_the_manifests_own_sentence(self): for entry in self.articles(): found = LEDE.search(self.page(entry["path"])) self.assertIsNotNone(found, f'{entry["path"]} has no lede') wanted = entry.get("lede") or entry["description"] self.assertEqual(wanted.strip(), found.group(1).replace("'", "'").strip()) class TheSidebarAndTheContentsFollowThePage(BuiltSite): """The left rail says where you are in the site; the right rail says where you are in the page. Neither is authored: one is the manifest, the other is the promoted slice's own h2s.""" def test_the_sidebar_marks_exactly_the_page_you_are_on(self): """The rail and its folded strip each mark the current page, so there is more than one marker — and every one of them names this page and no other.""" for entry in self.articles(): here = SIDE_HERE.findall(self.page(entry["path"])) self.assertTrue(here, f'{entry["path"]} does not mark itself in the ' f"sidebar") self.assertEqual({entry["path"]}, set(here), f'{entry["path"]}: a sidebar rendering marks ' f"some other page as here") def test_the_sidebar_lists_every_other_page_too(self): html = self.page("/concepts/stages/") for entry in self.articles(): self.assertIn(f'href="{entry["path"]}"', html, f'the sidebar has no link to {entry["path"]}') def test_the_header_nav_marks_the_section(self): self.assertIn('class="nav-link nav-here"', self.page("/concepts/stages/")) def test_on_this_page_is_the_bodys_own_h2s_in_order(self): """Not a subset and not a superset: the same anchors, the same order. A slice that grows a sub-heading grows a contents entry with nobody editing the site.""" for entry in self.articles(): html = self.page(entry["path"]) headings = HEADING_ID.findall(html) renderings = contents_lists(html) self.assertTrue(renderings or not headings, f'{entry["path"]}: headings but no contents list') for links in renderings: self.assertEqual(headings, links, f'{entry["path"]}: a contents rendering and ' f"the headings disagree") def test_a_page_with_sub_headings_really_has_a_contents_list(self): """Guards the test above against passing on two empty lists.""" html = self.page("/concepts/stages/") self.assertIn("On this page", html) self.assertIn('href="#backlog"', html) self.assertIn('href="#moving-a-task"', html) class PrevAndNextWalkTheFlow(BuiltSite): """The arrows follow the sidebar's order, because a reader who used one and then the other must not be sent somewhere else.""" def links(self, route: str) -> dict: return dict((direction, target) for direction, target in FLOW_LINK.findall(self.page(route))) def test_each_page_points_at_its_neighbours(self): order = [entry["path"] for entry in self.flow] for index, route in enumerate(order): found = self.links(route) self.assertEqual(order[index - 1] if index else None, found.get("prev"), f"{route}: wrong previous") self.assertEqual(order[index + 1] if index + 1 < len(order) else None, found.get("next"), f"{route}: wrong next") def test_the_ends_of_the_flow_have_one_arrow_each(self): first, last = self.flow[0]["path"], self.flow[-1]["path"] self.assertNotIn("prev", self.links(first)) self.assertIn("next", self.links(first)) self.assertIn("prev", self.links(last)) self.assertNotIn("next", self.links(last)) def test_an_absent_neighbour_keeps_its_slot(self): """`next →` sits on the right on the first page as on every other, which is a spacer in the markup rather than a rule in the stylesheet.""" html = self.page(self.flow[0]["path"]) flow = html[html.index('