Files
chemenu/tools/chemenu/tests/test_toc.py
T
torben f3c80747a5
CI / verify (push) Successful in 48s
Release / release (push) Successful in 41s
docs toc/verify: die .template-Form einer Referenzdatei steht im Dateisatz, Version 6.0.1 freigegeben (schliesst #106)
Files changed:
- CHANGES.md
- VERSION
- instructions/dev/doc-pull-through.md
- kb/CONVENTIONS.md.template
- tools/CONTRACT.md
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/tests/test_docs_verify.py
- tools/chemenu/tests/test_toc.py
- tools/chemenu/toc.py
2026-09-16 06:43:36 +02:00

232 lines
8.9 KiB
Python

from chemenu import blocks, toc
def _body(n_lines: int, sections: int) -> str:
"""A synthetic `# Title` + intro + `sections` `##` headings, `n_lines` long."""
lines = ["# Title", "", "Intro paragraph."]
per_section = max(1, (n_lines - len(lines) - sections) // sections) if sections else 0
for i in range(sections):
lines += ["", f"## Section {i}", ""]
lines += [f"Body line {j}." for j in range(per_section)]
while len(lines) < n_lines:
lines.append("Padding.")
return "\n".join(lines[:n_lines]) + "\n"
def test_needs_toc_is_false_at_the_threshold():
body = _body(toc.THRESHOLD, 3)
assert len(body.splitlines()) == toc.THRESHOLD
assert not toc.needs_toc(body)
def test_needs_toc_is_true_just_over_the_threshold():
body = _body(toc.THRESHOLD + 1, 3)
assert toc.needs_toc(body)
def test_needs_toc_measures_without_an_existing_region():
"""A file that shrank back under the threshold but still carries a stale
TOC region from when it was much longer must not count the region's own
lines toward keeping itself around - `needs_toc` strips it before
measuring. A 150-entry region is long enough on its own to push the
file over the threshold if it were (wrongly) counted."""
huge_region = toc.render([toc.Heading(level=2, text=f"Section {i}") for i in range(150)])
shrunk_with_stale_region = (
"# Title\n\nIntro paragraph.\n\n" + huge_region + "\n\n## Only Section\n\nOne line.\n"
)
assert len(shrunk_with_stale_region.splitlines()) > toc.THRESHOLD
assert not toc.needs_toc(shrunk_with_stale_region)
def test_iter_headings_skips_fenced_code():
body = "\n".join(
[
"# Title",
"",
"## Real Section",
"",
"```bash",
"# Not a heading",
"## Also not a heading",
"```",
"",
"### Real Subsection",
]
)
headings = toc.iter_headings(body)
assert [h.text for h in headings] == ["Real Section", "Real Subsection"]
assert [h.level for h in headings] == [2, 3]
def test_iter_headings_preserves_inline_code_in_heading_text():
"""Masking blanks inline code spans for *detection*, but the text has to
come back from the unmasked line or `` `instructions/dev/` `` would
render as blank space instead of its real content."""
body = "# Title\n\n## `instructions/dev/`\n"
headings = toc.iter_headings(body)
assert headings == [toc.Heading(level=2, text="`instructions/dev/`")]
def test_render_produces_nested_marker_region():
headings = [
toc.Heading(level=2, text="First"),
toc.Heading(level=3, text="Nested"),
toc.Heading(level=2, text="Second"),
]
region = toc.render(headings)
assert region.startswith(blocks.open_marker(toc.REGION_NAME))
assert region.endswith(blocks.close_marker(toc.REGION_NAME))
assert f"## {toc.HEADING_TEXT}" in region
assert "- [First](#first)" in region
assert " - [Nested](#nested)" in region
assert "- [Second](#second)" in region
def test_render_deduplicates_repeated_slugs():
headings = [toc.Heading(level=2, text="Steps"), toc.Heading(level=2, text="Steps")]
region = toc.render(headings)
assert "(#steps)" in region
assert "(#steps-1)" in region
def test_render_is_empty_for_no_headings():
assert toc.render([]) == ""
def test_upsert_inserts_before_the_first_level2_heading():
body = _body(toc.THRESHOLD + 20, 4)
result = toc.upsert(body)
assert result != body
marker_pos = result.index(blocks.open_marker(toc.REGION_NAME))
first_heading_pos = result.index("## Section 0")
assert marker_pos < first_heading_pos
# Title and intro paragraph still precede the region.
assert result.index("# Title") < marker_pos
assert result.index("Intro paragraph.") < marker_pos
def test_upsert_is_idempotent():
body = _body(toc.THRESHOLD + 30, 5)
once = toc.upsert(body)
twice = toc.upsert(once)
assert once == twice
def test_upsert_is_idempotent_without_a_trailing_newline():
"""A file missing its final newline (tools/CONTRACT.md and
types/type-spec.md both did, in the real repo) must not make the
"no region yet" and "region already exists" paths disagree on
round two."""
body = _body(toc.THRESHOLD + 20, 4).rstrip("\n")
once = toc.upsert(body)
twice = toc.upsert(once)
assert once == twice
def test_upsert_refreshes_a_stale_region_in_place():
body = _body(toc.THRESHOLD + 20, 3)
stale = toc.upsert(body)
# Rename a heading without touching the (now stale) TOC region.
changed = stale.replace("## Section 1", "## Renamed Section", 1)
refreshed = toc.upsert(changed)
assert "[Renamed Section](#renamed-section)" in refreshed
assert "[Section 1]" not in refreshed
def test_upsert_removes_the_region_once_the_file_shrinks():
body = _body(toc.THRESHOLD + 20, 3)
with_region = toc.upsert(body)
assert blocks.find(with_region, toc.REGION_NAME) is not None
shrunk = "# Title\n\nIntro paragraph.\n\n## Only Section\n\nOne line.\n"
result = toc.upsert(shrunk)
assert blocks.find(result, toc.REGION_NAME) is None
def test_stale_regions_is_false_right_after_upsert():
body = _body(toc.THRESHOLD + 20, 3)
once = toc.upsert(body)
assert toc.stale_regions(once) is False
def test_target_files_matches_the_documented_scope():
"""Integration check against the real repo: every agent-loaded category
AGENTS.md § File naming names - AGENTS.md itself, every stage contract,
kb/CONVENTIONS.md, every COLLECTION.md, the flat `instructions/**.md` form,
every type-spec, every `docs/` page - with `SKILL.md` the one exception."""
from chemenu import config
files = toc.target_files()
relatives = {f.relative_to(config.ROOT).as_posix() for f in files}
assert "AGENTS.md" in relatives
assert "kb/CONTRACT.md" in relatives
assert "kb/CONVENTIONS.md" in relatives
assert "types/type-spec.md" in relatives
assert "instructions/CONTRACT.md" in relatives
assert "instructions/dev/version-parts.md" in relatives # flat, still instructions/**.md
assert "types/source.md" in relatives # a page type-spec, not only the stage contract
assert "docs/version-model.md" in relatives
assert not any(rel.endswith("SKILL.md") for rel in relatives)
def test_target_files_takes_the_shipped_template_of_a_file_in_scope():
"""Integration check against the real repo: `kb/CONVENTIONS.md` is
instance-owned and crosses the distribution boundary as
`kb/CONVENTIONS.md.template`, so the template is the same document one step
earlier in its life and is maintained here too. Left out, nothing wrote the
region into it and nothing checked it - and the adopted copy inherited the
gap, which is how a fresh instance came to fail `docs verify` at the end of
its own setup."""
from chemenu import config
relatives = {f.relative_to(config.ROOT).as_posix() for f in toc.target_files()}
assert "kb/CONVENTIONS.md.template" in relatives
assert "kb/CONVENTIONS.md" in relatives # both forms, not one replacing the other
def test_target_files_takes_a_template_only_for_a_file_already_in_scope(tmp_path, monkeypatch):
"""The rule is "a file in scope carries its `.template` in with it", not
"every `.template` in the tree" - `USER.md.template` has no business here,
because `USER.md` itself is not reference material this region applies to."""
from chemenu import config
monkeypatch.setattr(config, "ROOT", tmp_path)
(tmp_path / "kb").mkdir()
for name in ("kb/CONVENTIONS.md", "kb/CONVENTIONS.md.template", "USER.md.template"):
(tmp_path / name).write_text("# A file\n", encoding="utf-8")
relatives = {f.relative_to(tmp_path).as_posix() for f in toc.target_files()}
assert relatives == {"kb/CONVENTIONS.md", "kb/CONVENTIONS.md.template"}
def test_target_files_lists_type_spec_once_despite_two_sources():
"""`types/type-spec.md` is both a stage contract and a `types/*.md` file;
the set in `target_files` is what keeps that from being a special case."""
from chemenu import config
relatives = [f.relative_to(config.ROOT).as_posix() for f in toc.target_files()]
assert relatives.count("types/type-spec.md") == 1
def test_strip_region_removes_what_types_describe_would_otherwise_echo():
body = (
"# A type\n\n"
f"{blocks.open_marker(toc.REGION_NAME)}\n## Contents\n\n- [X](#x)\n"
f"{blocks.close_marker(toc.REGION_NAME)}\n\n## X\n\nProse.\n"
)
stripped = toc.strip_region(body)
assert toc.REGION_NAME not in stripped
assert "## Contents" not in stripped
assert "## X" in stripped and "Prose." in stripped
# The heading keeps the blank line the region used to sit in, rather than
# being welded to the line above it.
assert "# A type\n\n## X" in stripped
def test_strip_region_leaves_a_body_that_never_had_one_alone():
body = "# A type\n\n## X\n\nProse.\n"
assert toc.strip_region(body) == body