Files
MeshTerm/tests/test_pathline.py
T
Jean-Pierre MartineauandClaude Fable 5 2c04fb5b96 Path line: path_text-parity factory, cursor-aware wrap, plain pin for tests
path_line() builds a PathLine from path_text's exact vocabulary (verified
character-for-character in tests), wrapped() carries the composer's
insertion cursor — riding the trailing arrow when its seam is the line
break — and conftest pins MESHTERM_POWERLINE=0 so screen assertions never
depend on which terminal runs the suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:30:50 -04:00

210 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Path-line widget tests: same words in both modes, three overflow shapes.
The widget's contract is exactness — plain mode must reproduce the app-wide arrow
presentation glyph for glyph, powerline mode must interlock chip fills through the
separator's foreground/background trick, and every overflow shape must respect its
cell budget — so these tests assert rendered strings and span styles, not vibes.
"""
from __future__ import annotations
import pytest
import meshterm.ui.pathline as pathline
from meshterm.ui.pathline import POWERLINE_SEP, PathHop, PathLine, path_line
from meshterm.ui.theme import node_style
from meshterm.ui.widgets import path_text
def _styles(text) -> dict[str, str]: # noqa: ANN001
"""Map each styled slice of a Text to its style string, for spot checks."""
return {text.plain[s.start:s.end]: str(s.style) for s in text.spans}
def _char_styles(text) -> list[tuple[str, str]]: # noqa: ANN001
"""Every character paired with its effective span style — exact-parity checks.
Span *boundaries* may legally differ between two builders (one appends a hash in
two pieces, the other in one); what must agree is the style each character lands
under, so the comparison is per cell, not per span.
"""
styles = [""] * len(text.plain)
for span in text.spans:
for i in range(span.start, span.end):
styles[i] = str(span.style)
return list(zip(text.plain, styles))
def test_plain_mode_matches_the_app_wide_arrow_presentation() -> None:
"""Names in their key hue, us white, keyless muted, arrows muted — the
``path_text`` look, reproduced exactly."""
line = PathLine(
[
PathHop("Alice", key="aa"),
PathHop("77", key="77", lit_bytes=1),
PathHop("you", you=True),
],
mode="plain",
)
text = line.text()
assert text.plain == "Alice → 77 → you"
styles = _styles(text)
assert styles["Alice"] == node_style("aa")
assert styles["you"] == "you"
assert styles["77"] == node_style("77") # the lit hash prefix carries the hue
def test_plain_mode_annotation_dim_and_custom_separator() -> None:
"""The trace ``(3d)`` note rides muted; a dim hop (and the arrow into it) fades;
a surface's own separator — the atlas trail's ``›`` — passes straight through."""
line = PathLine(
[PathHop("YUL", key="3d", annotation="3d"), PathHop("home", you=True, dim=True)],
mode="plain",
)
text = line.text()
assert text.plain == "YUL (3d) → home"
styles = _styles(text)
assert styles[" (3d)"] == "muted" # the annotation note, space included
assert styles["home"] == "faint"
assert styles[" → "] == "faint" # the arrow into a dim hop fades with it
trail = PathLine(
[PathHop("a"), PathHop("b")], mode="plain", separator=" › "
).text()
assert trail.plain == "a › b"
def test_empty_path_reads_as_the_callers_word() -> None:
"""A hopless line is the muted empty note, in every shape."""
assert PathLine([], mode="plain").text().plain == "direct"
assert PathLine([], mode="powerline", empty="direct — no relays").text().plain \
== "direct — no relays"
assert PathLine([], mode="plain").wrapped(40)[0].plain == "direct"
def test_chips_interlock_fills_through_the_separator() -> None:
"""Each seam's triangle wears the previous chip's fill as foreground on the next
chip's fill as background; the last points into the page (foreground only)."""
alice_fill = node_style("aa").split()[-1]
line = PathLine(
[PathHop("Alice", key="aa"), PathHop("you", you=True)], mode="powerline"
)
text = line.text()
assert text.plain == f" Alice {POWERLINE_SEP} you {POWERLINE_SEP}"
seam_styles = [str(s.style) for s in text.spans
if text.plain[s.start:s.end] == POWERLINE_SEP]
assert seam_styles == [f"{alice_fill} on #ffffff", "#ffffff"]
def test_chips_keep_the_same_words_and_honour_style_overrides() -> None:
"""Chips change colours and separators, never the words; an explicit style
override (hex or theme name) becomes the chip fill."""
hops = [PathHop("YUL", key="3d", annotation="3d"), PathHop("you", you=True)]
plain = PathLine(hops, mode="plain").text().plain
chips = PathLine(hops, mode="powerline").text().plain
assert plain.replace(" → ", " ") == chips.replace(POWERLINE_SEP, "").replace(" ", " ").strip()
themed = PathLine([PathHop("X", style="brand")], mode="powerline").text()
assert str(themed.spans[-1].style) == "#5eead4" # the closing edge, brand-filled
hexed = PathLine([PathHop("X", style="bold #123456")], mode="powerline").text()
assert str(hexed.spans[-1].style) == "#123456"
def test_auto_mode_follows_the_terminal_verdict(monkeypatch: pytest.MonkeyPatch) -> None:
"""``auto`` renders chips exactly when the terminal can draw them."""
hops = [PathHop("a"), PathHop("b")]
monkeypatch.setattr(pathline, "powerline_enabled", lambda: True)
assert POWERLINE_SEP in PathLine(hops).text().plain
monkeypatch.setattr(pathline, "powerline_enabled", lambda: False)
assert PathLine(hops).text().plain == "a → b"
def test_cursor_arrow_forces_plain_and_reverse_videos_the_seam() -> None:
"""The composer's insertion cursor needs a seam to sit in, so a cursor render
is always arrows — even in powerline mode — with the cursor arrow selected."""
hops = [PathHop("a"), PathHop("b")]
text = PathLine(hops, mode="powerline").text(cursor_arrow=0)
assert POWERLINE_SEP not in text.plain
assert text.plain == "a → b"
assert _styles(text)["→"] == "selected"
def test_ellipsized_elides_the_middle_and_keeps_both_endpoints() -> None:
"""A route reads origin and destination first, so the fit drops middle hops
behind ``⋯`` — never the tail a right-edge truncation would amputate."""
hops = [PathHop(label) for label in ("AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF")]
line = PathLine(hops, mode="plain")
assert line.ellipsized(200).plain == line.text().plain # fits → untouched
fitted = line.ellipsized(25)
assert fitted.cell_len <= 25
assert fitted.plain.startswith("AAAA")
assert fitted.plain.endswith("FFFF")
assert "⋯" in fitted.plain
def test_ellipsized_last_resort_truncates_a_single_giant_hop() -> None:
"""When even ``⋯ → last`` overflows, the classic ellipsis truncation steps in."""
line = PathLine([PathHop("AAAAAAAAAA"), PathHop("BBBBBBBBBB")], mode="plain")
fitted = line.ellipsized(8)
assert fitted.cell_len <= 8
assert fitted.plain.endswith("…")
def test_wrapped_breaks_at_hops_under_a_hanging_indent() -> None:
"""Plain lines that continue end with the ``→`` cue; continuations hang at the
indent; every line respects the full width."""
hops = [PathHop(label) for label in ("AAAA", "BBBB", "CCCC", "DDDD")]
lines = PathLine(hops, mode="plain").wrapped(16, indent=2)
assert [line.plain for line in lines] == ["AAAA → BBBB →", " CCCC → DDDD"]
assert all(line.cell_len <= 16 for line in lines)
def test_path_line_factory_matches_path_text_character_for_character() -> None:
"""The migration bridge: the trace flavour — device endpoints with annotated
hashes, a named hop, a prefix-lit unnamed hop, a dimmed tail — renders through
``path_line`` exactly as ``path_text`` renders it, character and style alike."""
names = {"aa11bb": "Alice", "3d63ab": "YUL"}
hops = [None, "aa11bb", "77ccddee", "3d63ab", None]
kwargs = dict(
prefix_bytes=2, self_name="Me", show_hash=True, hash_bytes=3,
device_hash="A1B2C3D4", dim_from=3,
)
old = path_text(hops, lambda h: names.get(h, h), **kwargs)
new = path_line(hops, lambda h: names.get(h, h), mode="plain", **kwargs).text()
assert _char_styles(new) == _char_styles(old)
def test_path_line_factory_matches_path_text_hash_as_name_flavour() -> None:
"""The message-paths flavour: an unnamed hop standing as its own muted identity
hash, annotated with its addressed byte — same parity guarantee."""
names = {"3d63abcdef00": "YUL"}
hops = ["3d63abcdef00", "e839f2aabb11"]
kwargs = dict(prefix_bytes=3, show_hash=True, hash_bytes=1, hash_as_name=True)
old = path_text(hops, lambda h: names.get(h, h), **kwargs)
new = path_line(hops, lambda h: names.get(h, h), mode="plain", **kwargs).text()
assert _char_styles(new) == _char_styles(old)
assert path_line([], lambda h: h).text().plain == path_text([], lambda h: h).plain
def test_wrapped_carries_the_cursor_even_onto_a_line_break() -> None:
"""A cursor render is plain even for a powerline line; a cursor sitting mid-group
reverse-videos its arrow, and one sitting exactly on the break rides that line's
trailing cue — the insertion point is always visible."""
hops = [PathHop(label) for label in ("AAAA", "BBBB", "CCCC", "DDDD")]
line = PathLine(hops, mode="powerline")
mid = line.wrapped(16, indent=2, cursor_arrow=0)
assert [text.plain for text in mid] == ["AAAA → BBBB →", " CCCC → DDDD"]
assert any(str(s.style) == "selected" for s in mid[0].spans)
assert all(POWERLINE_SEP not in text.plain for text in mid)
on_break = line.wrapped(16, indent=2, cursor_arrow=1) # the seam that broke
assert str(on_break[0].spans[-1].style) == "selected"
assert not any(str(s.style) == "selected" for s in on_break[1].spans)
def test_wrapped_chip_lines_each_close_their_pointed_edge() -> None:
"""Chip wrapping never splits a chip; every line ends on the pointed edge."""
hops = [PathHop(label) for label in ("AAAA", "BBBB", "CCCC", "DDDD")]
lines = PathLine(hops, mode="powerline").wrapped(16, indent=2)
assert len(lines) == 2
assert lines[1].plain.startswith(" ")
assert all(line.plain.endswith(POWERLINE_SEP) for line in lines)
assert all(line.cell_len <= 16 for line in lines)