"""The drawer renders a wrapped list item as one item (task 41). `md()` in board.html used to treat every *physical* line inside a block as a unit. Task files are hard-wrapped at ~74 columns, so the second line of an item became its own bullet, `- [ ]` rendered as a literal bracket pair, nested lists flattened, and paragraphs kept the author's ragged edge via `
`. board.html is a single file with inline JS and no frontend test runner, so this suite lifts `esc()` and `md()` straight out of the page and runs them under node — the renderer is a pure function of its input, so its actual output is what to assert on. Node is not a dependency of bench itself, so those checks skip when it is absent; the source-level invariants at the bottom always run and are in the same style as the board's other `test_*.py` checks on board.html. python3 -m unittest discover -s tests -v """ from __future__ import annotations import re import shutil import subprocess import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] BOARD = ROOT / "manager" / "core" / "board.html" NODE = shutil.which("node") HTML = BOARD.read_text(encoding="utf-8") def lift(pattern: str, what: str) -> str: m = re.search(pattern, HTML, re.M | re.S) assert m, f"board.html lost {what}" return m.group(0) HARNESS = ( lift(r"^const esc = \(s\) =>.*?\n.*?\n", "its esc() helper") + lift(r"^function md\(src\) \{\n.*?\n\}\n", "its md() renderer") + "process.stdout.write(md(require('fs').readFileSync(0, 'utf8')));\n" ) class RendererCase(unittest.TestCase): """Base: run the page's own md() over a markdown string.""" @classmethod def setUpClass(cls): if not NODE: return cls._dir = tempfile.TemporaryDirectory() cls.js = Path(cls._dir.name) / "md.js" cls.js.write_text(HARNESS, encoding="utf-8") @classmethod def tearDownClass(cls): if NODE: cls._dir.cleanup() def render(self, src: str) -> str: out = subprocess.run([NODE, str(self.js)], input=src, text=True, capture_output=True) self.assertEqual(out.returncode, 0, out.stderr) return out.stdout INNERMOST = re.compile(r"<(ul|ol)>((?:(?!<(?:ul|ol)>).)*?)", re.S) def items(self, html: str) -> list[str]: """The text of each top-level
  • : nested lists dropped, markup stripped, so a test can assert on what the reader sees.""" body = re.sub(r"\s*$", "", re.sub(r"^\s*<(ul|ol)>", "", html)) while self.INNERMOST.search(body): # peel nested lists off body = self.INNERMOST.sub("", body) body = re.sub(r'', "", body) # the tick glyph return [re.sub(r"<[^>]+>", "", li).strip() for li in re.findall(r"]*>(.*?)
  • ", body, re.S)] @unittest.skipUnless(NODE, "node is needed to run the page's own md()") class WrappedItemsTests(RendererCase): """One bullet per item, however the author wrapped it.""" def test_a_wrapped_item_is_one_item(self): """The live bug: 'serves the built landing page' / 'over' were two bullets because the source line broke between them.""" html = self.render( "- Given a request for the site, when it is served, then the\n" " worker serves the built landing page\n" "- A second item\n") self.assertEqual(html.count("", html) def test_ordered_lists_group_the_same_way(self): html = self.render( "1. The board creates a git worktree on a new\n" " branch from the newest main it can see\n" "2. The agent works in the worktree\n") self.assertTrue(html.startswith("
      "), html[:40]) self.assertEqual(len(self.items(html)), 2) @unittest.skipUnless(NODE, "node is needed to run the page's own md()") class TaskListTests(RendererCase): """`- [ ]` / `- [x]` become checkboxes, and only ever glyphs.""" SRC = ("- [ ] Given an Acceptance list whose items wrap, then there\n" " is exactly one bullet per item\n" "- [x] Fenced code blocks are unchanged\n") def test_no_bracket_survives_as_text(self): html = self.render(self.SRC) text = " ".join(self.items(html)) self.assertNotIn("[", text) self.assertNotIn("]", text) def test_the_item_text_survives_beside_the_box(self): self.assertEqual( self.items(self.render(self.SRC)), ["Given an Acceptance list whose items wrap, then there " "is exactly one bullet per item", "Fenced code blocks are unchanged"]) def test_ticked_and_unticked_are_distinguishable(self): html = self.render(self.SRC) lis = re.findall(r"]*)>", html) self.assertEqual(len(lis), 2) self.assertIn('class="tick"', lis[0]) # open: neutral self.assertIn('class="tick on"', lis[1]) # done: settled self.assertEqual(html.count(' and never a handler: clicking it can do nothing, so it cannot quietly edit the file.""" html = self.render(self.SRC + "\n- [X] upper case counts as ticked\n") self.assertNotIn("]*)>", html)[-1]) def test_a_bracket_that_is_not_a_checkbox_is_left_alone(self): items = self.items(self.render("- [see the spec](../ref.md) explains it\n")) self.assertEqual(items, ["see the spec explains it"]) self.assertIn('href="../ref.md"', self.render( "- [see the spec](../ref.md) explains it\n")) @unittest.skipUnless(NODE, "node is needed to run the page's own md()") class NestingTests(RendererCase): """Children indent under their parent instead of flattening beside it.""" def test_a_nested_list_is_a_child_of_its_parent_item(self): html = self.render( "- parent one\n" " - child a\n" " - child b\n" "- parent two\n") self.assertEqual(self.items(html), ["parent one", "parent two"]) self.assertRegex(html, r"parent one
      • child a
      • child b
      ") def test_the_nesting_closes_when_the_indent_returns(self): html = self.render( "- parent one\n" " - child a\n" "- parent two\n") self.assertEqual(html.count("
        "), 2) self.assertEqual(html.count("
      "), 2) self.assertTrue(html.endswith("")) def test_a_wrapped_child_is_still_one_child(self): html = self.render( "- parent\n" " - the child wraps across\n" " two source lines\n") self.assertIn("
    1. the child wraps across two source lines
    2. ", html) def test_deeper_nesting_degrades_rather_than_breaks(self): html = self.render( "- a\n - b\n - c\n- d\n") self.assertEqual(html.count("
        "), html.count("
      ")) for text in ("a", "b", "c", "d"): self.assertIn(f"
    3. {text}", html) def test_a_nested_ordered_list_under_a_bullet_keeps_its_tag(self): html = self.render("- parent\n 1. first\n 2. second\n") self.assertIn("
      1. first
      2. second
      ", html) @unittest.skipUnless(NODE, "node is needed to run the page's own md()") class ReflowTests(RendererCase): """Prose wraps to the drawer, not to the author's editor.""" def test_a_paragraph_has_no_hard_break(self): html = self.render( "The renderer is line-based and the task files are\n" "hard-wrapped, so almost every list on the board comes\n" "out wrong.\n") self.assertNotIn("
      ", html) self.assertIn("task files are hard-wrapped", html) def test_a_blockquote_reflows_too(self): html = self.render("> a quoted line\n> and its continuation\n" .replace(">", ">")) self.assertNotIn("
      ", html) self.assertIn("a quoted line and its continuation", html) def test_no_br_survives_anywhere_in_the_corpus(self): """Every card on the board plus AGENTS.md: the author's wrap column must not reach the browser.""" docs = sorted(ROOT.glob("tasks/*/*.md")) + [ROOT / "AGENTS.md"] self.assertGreater(len(docs), 5, "no task files found to render") for doc in docs: with self.subTest(doc=doc.relative_to(ROOT).as_posix()): self.assertNotIn("
      ", self.render( doc.read_text(encoding="utf-8"))) def source_bullets(src: str) -> int: """How many logical list items a document contains, counted from the source the way a reader counts them: markers only, fences skipped, and only in blocks that actually open with one.""" marker = re.compile(r"^[ \t]*(?:[-*]|\d+\.)[ \t]+") total, fence = 0, False for block in re.split(r"\n{2,}", src): ticks = block.count("```") if fence or block.startswith("```"): fence = (ticks % 2 == 0) if fence else (ticks % 2 == 1) continue lines = block.rstrip().split("\n") if len(lines) >= 2 and re.match(r"^\s*\|.*\|\s*$", lines[0]) \ and re.match(r"^\s*\|[\s:|-]+\|\s*$", lines[1]): continue # a table, not a list if marker.match(lines[0]): total += sum(1 for l in lines if marker.match(l)) return total @unittest.skipUnless(NODE, "node is needed to run the page's own md()") class CorpusTests(RendererCase): """The whole board, not a fixture: one bullet per marker, no more.""" def docs(self) -> list[Path]: found = sorted(ROOT.glob("tasks/*/*.md")) + [ROOT / "AGENTS.md"] self.assertGreater(len(found), 5, "no task files found to render") return found def test_every_document_renders_one_bullet_per_marker(self): """Before the fix a hard-wrapped item produced a bullet per source line; this is the acceptance criterion applied to every card.""" for doc in self.docs(): with self.subTest(doc=doc.relative_to(ROOT).as_posix()): src = doc.read_text(encoding="utf-8") self.assertEqual(self.render(src).count("'), html.count("(.*?)", html, re.S).group(1) self.assertEqual(body.rstrip("\n").split("\n"), [ ".task-manager/", "├── AGENTS.md ← This file", "│ ├── VERSION, board.py", "└── manager/", ]) def test_a_fence_spanning_blank_lines_still_closes(self): """The fence state machine spans blocks — a blank line inside a fence must not end it, and a bullet inside must stay literal.""" html = self.render("```\nfirst\n\n- not a bullet\n```\n\nafter\n") self.assertEqual(html.count("
      "), 1)
              self.assertNotIn("
    4. ", html) self.assertIn("

      after

      ", html) def test_a_table_after_a_list_is_still_a_table(self): """Card 30's wrong/right table is the live case.""" html = self.render( "- a bullet that wraps\n onto a second line\n\n" "| Turn 1 says | bench actually |\n| --- | --- |\n" "| `bench.toml` | `manager/local/.env` |\n") self.assertIn("", html) self.assertIn("", html) self.assertIn("", html) self.assertEqual(html.count("What to build", html) self.assertIn("
      ", html) self.assertIn("
    5. item
    6. ", html) def test_html_in_the_source_is_still_escaped(self): html = self.render("- an item with in it\n") self.assertNotIn("
      Turn 1 saysbench.toml