generated from coulomb/repo-seed
A CommonMark wikilink extension: extract [[Target]] / [[Target|label]] from a page body (skipping fenced + inline code, preserving offsets), and resolve each target through the union — resolved is a link, unresolved is a createable red-link (never a dropped reference). CamelCase auto-linking is off by default, opt-in per space, and never double-counts a target already inside [[...]]. Link model + resolution are core; rendering stays L6. New views/ package. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.6 KiB
Python
70 lines
2.6 KiB
Python
"""Tests for the wikilink + red-link model (SHARD-WP-0010 T1)."""
|
|
|
|
from shard_wiki.adapters import FolderAdapter
|
|
from shard_wiki.union import ResolutionKind, UnionGraph
|
|
from shard_wiki.views import extract_links, resolve_links
|
|
|
|
|
|
def _shard(tmp_path, name, files):
|
|
root = tmp_path / name
|
|
for rel, text in files.items():
|
|
p = root / rel
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_text(text, encoding="utf-8")
|
|
return FolderAdapter(name, root)
|
|
|
|
|
|
def test_extracts_plain_and_labelled_links():
|
|
links = extract_links("See [[Home]] and [[Index|the index]].")
|
|
assert [(link.target, link.label, link.text) for link in links] == [
|
|
("Home", None, "Home"),
|
|
("Index", "the index", "the index"),
|
|
]
|
|
|
|
|
|
def test_links_carry_body_offsets_in_document_order():
|
|
body = "a [[One]] b [[Two]]"
|
|
links = extract_links(body)
|
|
assert [link.target for link in links] == ["One", "Two"]
|
|
s, e = links[0].span
|
|
assert body[s:e] == "[[One]]"
|
|
|
|
|
|
def test_code_regions_are_not_scanned():
|
|
body = "real [[Home]]\n```\n[[NotALink]]\n```\ninline `[[AlsoNot]]` done"
|
|
targets = [link.target for link in extract_links(body)]
|
|
assert targets == ["Home"]
|
|
|
|
|
|
def test_camelcase_off_by_default_then_opt_in():
|
|
body = "FrontPage links to [[Home]]"
|
|
assert [link.target for link in extract_links(body)] == ["Home"] # CamelCase ignored
|
|
on = extract_links(body, camelcase=True)
|
|
assert {link.target for link in on} == {"FrontPage", "Home"}
|
|
assert next(link for link in on if link.target == "FrontPage").auto is True
|
|
|
|
|
|
def test_camelcase_does_not_double_count_inside_explicit_link():
|
|
# [[FrontPage]] is one explicit link, not also a CamelCase auto-link.
|
|
links = extract_links("[[FrontPage]]", camelcase=True)
|
|
assert len(links) == 1
|
|
assert links[0].auto is False
|
|
|
|
|
|
def test_resolve_links_distinguishes_link_from_red_link(tmp_path):
|
|
u = UnionGraph("space")
|
|
u.attach(_shard(tmp_path, "shardA", {"Home.md": "home"}))
|
|
resolved = resolve_links(u, "[[Home]] and [[Ghost]]")
|
|
by_target = {r.link.target: r for r in resolved}
|
|
assert by_target["Home"].resolution.kind is ResolutionKind.SINGLE
|
|
assert by_target["Home"].is_red_link is False
|
|
assert by_target["Ghost"].is_red_link is True # unresolved → createable red-link
|
|
|
|
|
|
def test_resolve_links_surfaces_chorus(tmp_path):
|
|
u = UnionGraph("space")
|
|
u.attach(_shard(tmp_path, "shardA", {"Home.md": "A"}))
|
|
u.attach(_shard(tmp_path, "shardB", {"Home.md": "B"}))
|
|
(resolved,) = resolve_links(u, "[[Home]]")
|
|
assert resolved.resolution.kind is ResolutionKind.CHORUS
|