Files
MeshTerm/tests/test_message_paths_screen.py
T
Jean-Pierre MartineauandClaude Opus 4.8 ed391ab16e Trophy case + route graph: node-type marks, SNR colour, reliability
Route graph (both the record card and the Message paths dialog):
- Relays now wear their own map marker — ▲ repeater, ■ room, ◉ sensor —
  instead of a generic ●, via a node-type resolver threaded into
  route_graph_style (built from contacts; an untyped relay keeps the dot).
- A shared node-type key (★ you ▲ repeater ● node ■ room ◉ sensor) sits
  under every fan, minted once as widgets.node_type_legend.
- The drawn path is coloured by SNR (the SNR palette): the record's walk
  by its weakest link, the Message paths selection by its reception SNR;
  unselected paths stay gray. Per-path, not per-hop — overheard frames
  only carry the reception reading.

Record card:
- New 'reliability' stat: the success rate of traces aimed at the walk's
  far node (the one delivery figure history can answer — a failed trace
  records its target, not its route), with the sample count beside it.
- Area drawing reworked per feedback: our star is drawn last so it is
  never hidden, hops are pinned in their node-name hue (star stays
  yellow), the fill is one flat tone (even-odd shades only the enclosed
  side; no dimmer interior wash), and the box is larger.

New shared helpers: theme.snr_rgb, widgets.name_rgb,
trace_runner.make_node_type_resolver, repository.target_trace_counts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:26:47 -04:00

122 lines
5.3 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.
"""Message-paths dialog tests: the graph-over-rows view behind the chat's ^P.
Driven headless like the atlas tests: render_body is pure lines-out, handle() pure
state, so the selection cursor, the horizontal line scroll, and the selected-path
labelling are all assertable without a terminal.
"""
from __future__ import annotations
import re
from datetime import timedelta
from meshterm.core.models import ChatMessage, utcnow
from meshterm.services.message_paths import Arrival
from meshterm.ui.message_paths_screen import MessagePathsScreen
def _resolve(hop: str) -> str:
return {"3d63": "YUL-Cartierville", "a1b2": "Waymarker"}.get(hop, hop)
def _plain(lines: list[str]) -> str:
return re.sub(r"\x1b\[[0-9;]*m", "", "\n".join(lines))
def _screen(arrivals: list[Arrival], **kwargs) -> MessagePathsScreen:
message = ChatMessage(text="on my way", is_channel=True, created_at=utcnow())
defaults = dict(
matched=True, resolve=_resolve, prefix_bytes=1, self_name="Homestead",
summary="heard twice", source="Alice",
)
defaults.update(kwargs)
return MessagePathsScreen(message, arrivals, **defaults)
def _arrivals() -> list[Arrival]:
now = utcnow()
return [
Arrival(when=now, hops=("3d63",), snr=4.0),
Arrival(when=now + timedelta(seconds=2), hops=("a1b2", "77aa"), snr=-2.0),
]
def test_paths_screen_renders_graph_rows_and_cursor() -> None:
"""The quoted text, the graph endpoints, and one selectable row per arrival."""
screen = _screen(_arrivals())
body = _plain(screen.render_body(76))
assert "“on my way”" in body
assert "Alice" in body and "Homestead" in body # origin and us, on the graph
assert body.count("via") == 2 # one row per arrival
assert "❯" in body
assert "colour = SNR of selected" in body # the graph caption
assert "★ you" in body and "▲ repeater" in body # the node-type legend
assert screen.cursor_line() is not None
def test_paths_screen_labels_every_relay_with_its_hash_byte() -> None:
"""Graph relays carry their first hash byte, both paths at once; names stay
in the rows, annotated with the same byte."""
screen = _screen(_arrivals())
body = _plain(screen.render_body(76))
graph = body.split("origin →")[0]
for byte in ("3d", "a1", "77"): # every relay labelled, selected or not
assert byte in graph
assert "YUL-Cartier" not in graph and "Waymarker" not in graph
assert "YUL-Cartierville (3d)" in body # the rows carry name + hash byte
assert "Waymarker (a1)" in body
def test_paths_screen_colours_selected_path_by_snr_over_gray() -> None:
"""The selected path's edges take its arrival's SNR hue; the unused path stays gray."""
screen = _screen(_arrivals()) # selected arrival's SNR is +4.0 dB → the "ok" band
raw = "\n".join(screen.render_body(76)).split("origin →")[0]
assert "38;2;251;191;36" in raw # snr.ok amber — the selected path, by SNR
assert "38;2;110;110;110" in raw # the other path, gray beneath it
screen.handle("down") # select the weaker arrival (−2.0 dB, still "ok")
raw2 = "\n".join(screen.render_body(76)).split("origin →")[0]
assert "38;2;251;191;36" in raw2 # the newly selected path recolours
def test_paths_screen_marks_a_repeater_relay_with_its_triangle() -> None:
"""A relay whose type resolves to a repeater draws ▲, not the generic dot."""
screen = _screen(_arrivals(), type_of=lambda h: 2 if h == "3d63" else None)
graph = _plain(screen.render_body(76)).split("origin →")[0]
assert "▲" in graph # the repeater relay wears its map glyph
screen.handle("down")
raw = "\n".join(screen.render_body(76)).split("origin →")[0]
assert "38;2;255;255;255" in raw and "38;2;110;110;110" in raw
def test_paths_screen_scrolls_the_selected_line_sideways() -> None:
"""→ shifts the whole selected row under a leading …; ↑↓ snap it back."""
now = utcnow()
long = Arrival(when=now, hops=tuple(f"{i:02x}{i:02x}" for i in range(12)), snr=1.0)
screen = _screen([long, Arrival(when=now, hops=(), snr=None)])
narrow = 40
before = _plain(screen.render_body(narrow))
selected_before = next(ln for ln in before.split("\n") if ln.startswith("❯"))
assert screen._hmax > 0 # the row genuinely overflows at this width
for _ in range(3):
screen.handle("right")
after = _plain(screen.render_body(narrow))
selected_after = next(ln for ln in after.split("\n") if ln.startswith("❯"))
assert selected_after != selected_before
assert "…" in selected_after # the left edge marks the hidden head
stamp = long.when.astimezone().strftime("%H:%M:%S")
assert stamp in selected_before and stamp not in selected_after # whole line shifts
screen.handle("down")
assert screen._hshift == 0 # only the selected line stays scrolled
def test_paths_screen_direct_arrival_and_empty_state() -> None:
"""A hop-less arrival reads direct; no arrivals at all explain themselves."""
now = utcnow()
screen = _screen([Arrival(when=now, hops=(), snr=2.5)])
body = _plain(screen.render_body(76))
assert "direct" in body
empty = _screen([], matched=False)
body = _plain(empty.render_body(76))
assert "No direct-message frames logged in the window." in body
assert empty.footer_hint == "Esc close"