Files
MeshTerm/tests/test_widgets.py
T
Jean-Pierre MartineauandClaude Opus 5 92d31a3191 Every file is spelled the way the formatter spells it
`ruff format .` across the repo: 153 files, no behaviour change. This is the
second gate CONTRIBUTING.md promises, and until now it reported 154 files needing
reformat — so a first contributor ran the command the guide told them to run and
could not tell their own diff from the background.

Landed on its own, with nothing else in it, because a whole-repo reformat is only
safe to skip past in blame if it is provably nothing but layout. It was checked
that way rather than assumed: every changed file was formatted in memory and
compared by full `ast.dump()`, and 152 of 153 come out structurally identical
(the one exception was a docstring opening on a quote character, fixed in the
commit before this one). The string and comment token streams were diffed
separately, so a quote flip or an edited comment could not hide inside a
re-layout — five quote-style normalisations and sixteen implicit-concatenation
joins, all value-identical, and one commented-out line gaining the space after
its `#`. `meshterm specimen` is byte-identical on both platforms, and the suite
is 1888 green, gallery gate and `test_theme16` included.

What actually changed, in order of how much of the diff it is: wrapped calls and
signatures rejoin onto one line where they fit inside the hundred columns; the
magic trailing comma expands what it is there to expand; complex slices gain
their spaces (`[len(noise) :]`); a few redundant parens go. The tables that are
laid out to be read were fenced in the previous commit and are untouched here.

The aligned trailing-comment column is gone from about ninety lanes that were not
fenced — including the `heat.*` ladder in `MESH_THEME`, which cannot be fenced
where it sits. Every comment stays on the entry it describes, so nothing there
reads wrong; it just no longer lines up.

Add this commit to `.git-blame-ignore-revs`, which follows immediately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 01:07:19 -04:00

278 lines
11 KiB
Python

"""Shared-widget helper tests: the canonical formatting primitives every screen leans on.
Most widgets are exercised through their host screens' tests; what lives here are the
pure text helpers whose exact output *is* the app-wide convention — get these right once
and every caller inherits it.
"""
from __future__ import annotations
from rich.style import Style
from rich.text import Text
from meshterm.ui.pathline import (
POWERLINE_ROUND_CLOSE,
POWERLINE_ROUND_OPEN,
PathHop,
PathLine,
)
from meshterm.ui.theme import node_style
from meshterm.ui.widgets import (
_format_age,
format_ago,
name_chip,
path_text,
revisit_note,
)
def _span_style(text: Text, run: str) -> Style:
"""The style over ``run`` in ``text``, parsed — pathline spans carry style *strings*."""
return Style.parse(next(str(s.style) for s in text.spans if text.plain[s.start : s.end] == run))
def test_format_age_is_the_bare_column_form() -> None:
"""The lane form stays suffix-free at every magnitude, for aligned age columns."""
assert _format_age(None) == "never"
assert _format_age(5) == "now"
assert _format_age(90) == "1m"
assert _format_age(7200) == "2h"
assert _format_age(180000) == "2d"
assert _format_age(1300000) == "2w"
def test_format_ago_speaks_grammatical_prose() -> None:
"""The prose form says "5m ago" but bare "now"/"never" — never "now ago"."""
assert format_ago(5) == "now"
assert format_ago(None) == "never"
assert format_ago(90) == "1m ago"
assert format_ago(7200) == "2h ago"
def _resolve(hop: str) -> str:
return {"aa": "Alice", "3d": "Hub"}.get(hop, hop)
def test_path_text_names_hops_and_keeps_hashes_bare() -> None:
"""Named hops read as their name alone; unnamed ones as their bare hash."""
text = path_text(["aa", "77", "3d"], _resolve)
assert text.plain == "Alice → 77 → Hub" # no parenthesized hash after a name
def test_path_text_marks_us_white_and_names_in_their_hue() -> None:
"""Our own node takes the white ``you`` style; other names their palette hue."""
text = path_text(["aa", "3d"], _resolve, self_name="Alice")
styles = {text.plain[s.start : s.end]: str(s.style) for s in text.spans}
assert styles.get("Alice") == "you"
assert "Hub" in styles and styles["Hub"] != "you"
def test_path_text_empty_reads_as_direct() -> None:
"""No hops (or only empty tokens) renders the caller's empty word, muted."""
assert path_text([], _resolve).plain == "direct"
assert path_text(["", ""], _resolve).plain == "direct"
assert path_text([], _resolve, empty="direct — no relays").plain == "direct — no relays"
def test_path_text_greys_an_unnamed_hop_whole() -> None:
"""``path_text`` greys an unnamed hop whole.
An unresolvable hop is an unknown node: its hash reads ``node.unknown`` end to
end, the prefix never lit, because colour marks an identified node — the path
graph's rule.
"""
from meshterm.ui.theme import node_style
text = path_text(["77bb"], _resolve, prefix_bytes=1)
styles = {text.plain[s.start : s.end]: str(s.style) for s in text.spans}
assert styles.get("77bb") == "node.unknown"
assert not any(str(s.style) == node_style("77bb") for s in text.spans)
def test_path_text_hash_as_name_shows_grey_identity_hash() -> None:
"""An unnamed hop stands in its own hash at the mode width, muted, plus its byte."""
text = path_text(
["e839f2ab"],
_resolve,
prefix_bytes=3,
show_hash=True,
hash_bytes=1,
hash_as_name=True,
)
assert text.plain == "e839f2 (e8)" # mode width identity, then the addressed byte
styles = {text.plain[s.start : s.end]: str(s.style) for s in text.spans}
assert styles.get("e839f2") == "node.unknown" # grey — colour is the "this is a name" cue
from meshterm.ui.theme import node_style
assert not any(str(s.style) == node_style("e839f2ab") for s in text.spans) # no prefix lit
def test_path_text_hash_as_name_drops_the_byte_when_it_is_the_whole_hash() -> None:
"""When the mode width is one byte, the identity already is the byte — no ``(e8)``."""
text = path_text(
["e839f2ab"],
_resolve,
prefix_bytes=1,
show_hash=True,
hash_bytes=1,
hash_as_name=True,
)
assert text.plain == "e8"
def test_path_text_trace_flavour_annotates_hashes_and_brackets_us() -> None:
"""show_hash appends the addressed hash to names; None hops are our device."""
text = path_text(
[None, "aa", "77bb", None],
_resolve,
self_name="Homestead",
show_hash=True,
hash_bytes=1,
device_hash="c0ffee",
)
assert text.plain == "Homestead (c0) → Alice (aa) → 77 → Homestead (c0)"
def test_path_text_dims_the_tail_from_dim_from() -> None:
"""The mirrored return leg (and the arrows into it) render faint."""
text = path_text(
[None, "aa", "3d", "aa", None],
_resolve,
self_name="us",
show_hash=True,
dim_from=3,
)
styles = [(text.plain[s.start : s.end], str(s.style)) for s in text.spans]
assert ("us", "you") in styles # the departure keeps the white you
assert ("us", "faint") in styles # the landing back on us is faint
assert any(run.startswith("Alice") and style == "faint" for run, style in styles)
assert any(run.startswith("Hub") and style != "faint" for run, style in styles)
# --- the name→key resolver and the keyless-stays-muted rule --------------------------
def test_make_name_key_resolver_contacts_win_over_stored_names() -> None:
"""Contacts resolve first; stored advert names fill in strangers; misses are None."""
from meshterm.core.models import Contact
from meshterm.services.trace_runner import make_name_key_resolver
contacts = [Contact(name="Alice", public_key="d4" + "0" * 62)]
stored = {"60aabbccdd11": "Bob", "77ee00112233": "Alice"} # a stale stored Alice
key_of = make_name_key_resolver(contacts, stored)
assert key_of("Alice") == "d4" + "0" * 62 # the contact's key, not the stored id
assert key_of("alice") == "d4" + "0" * 62 # casefolded
assert key_of("Bob") == "60aabbccdd11" # a stored-name stranger still lands
assert key_of("Zed") is None # nobody carries the name
def test_name_style_without_a_key_is_the_unknown_grey() -> None:
"""A keyless name has no hue — colour is reserved for keyed identities."""
from meshterm.ui.theme import name_style, node_style
assert name_style("Stranger") == "node.unknown"
keyed = name_style("Alice", "d4" + "0" * 62)
assert keyed == node_style("d4" + "0" * 62) and keyed.startswith("bold #")
def test_name_rgb_keyless_lands_on_the_muted_grey() -> None:
"""The raster twin: a keyless name's RGB is the muted grey, never a crash."""
from meshterm.ui.widgets import name_rgb
assert name_rgb("Stranger") == (148, 163, 184)
assert name_rgb("Alice", "d4" + "0" * 62) != (148, 163, 184)
def test_route_graph_source_label_takes_its_resolved_keys_hue() -> None:
"""The graph's left endpoint hue rides key_of: resolved → the key's hue, else muted."""
from meshterm.ui.widgets import name_rgb, route_graph_style
_, _, rgb_known = route_graph_style(
resolve=lambda h: h,
self_name="us",
source="Alice",
key_of=lambda name: "d4" + "0" * 62 if name == "Alice" else None,
)
from meshterm.ui.pathgraph import SRC_NODE
assert rgb_known(SRC_NODE) == name_rgb("Alice", "d4" + "0" * 62)
_, _, rgb_unknown = route_graph_style(
resolve=lambda h: h,
self_name="us",
source="Alice",
)
assert rgb_unknown(SRC_NODE) == (148, 163, 184) # unresolvable origin stays muted
def test_revisit_note_names_the_repeats_and_refuses_to_guess_why() -> None:
"""The one line a graph drawing a node twice owes its reader — both readings, neither picked."""
note = revisit_note(["aa"], _resolve)
assert note is not None
assert note.plain == "⚠ Alice repeats — a loop, or two nodes sharing one hash"
styles = {note.plain[s.start : s.end]: str(s.style) for s in note.spans}
assert styles.get("⚠ ") == "warn" # the app's warn mark, themed
assert styles.get("Alice") not in (None, "warn") # the name keeps its own node hue
def test_revisit_note_lists_every_repeated_hop() -> None:
"""More than one hop repeated names them all, comma-joined, still one sentence."""
note = revisit_note(["aa", "3d"], _resolve)
assert note is not None
assert note.plain.startswith("⚠ Alice, Hub repeats — ")
assert len(note.plain) <= 72 # the footer/row budget every surface renders it inside
def test_revisit_note_is_absent_when_nothing_repeats() -> None:
"""No repeats, no line — the warning is evidence, never chrome."""
assert revisit_note([], _resolve) is None
assert revisit_note(()) is None
def test_name_chip_is_a_path_line_of_one_hop() -> None:
"""The framing isn't imitated — it *is* the route language, so it can't drift from it."""
chip = name_chip("Alice", "a1b2")
hop = PathLine([PathHop("Alice", key="a1b2")]).text()
assert chip.plain == hop.plain
assert [(s.start, s.end, str(s.style)) for s in chip.spans] == [
(s.start, s.end, str(s.style)) for s in hop.spans
]
def test_name_chip_wears_the_hue_as_its_fill_padded_like_every_chip(powerline) -> None:
"""A route's colour scheme: the node's hue fills the chip, the ink on it is dark."""
powerline(True)
chip = name_chip("Alice", "a1b2")
assert chip.plain.strip(POWERLINE_ROUND_OPEN + POWERLINE_ROUND_CLOSE) == " Alice "
ink = _span_style(chip, "Alice")
assert ink.bgcolor.name == node_style("a1b2").removeprefix("bold ") # the hue
assert ink.color.name == "#0f172a" # the chip ink every hop is lettered in
def test_name_chip_draws_us_as_the_star_never_as_our_name(powerline) -> None:
"""Our end of a route is the map's own marker; a sender label is no different."""
powerline(True)
chip = name_chip("you", you=True)
assert "★" in chip.plain and "you" not in chip.plain
assert _span_style(chip, "★").bgcolor.name == "#3f3f46" # neutral grey, not a hue
def test_name_chip_greys_a_node_it_cannot_place(powerline) -> None:
"""No key, no hue — colour is reserved for keyed identities, in a chip as in a route."""
powerline(True)
keyless = name_chip("·")
assert _span_style(keyless, "·").bgcolor.name == "#64748b" # the keyless grey
def test_name_chip_falls_back_to_the_bare_name_without_powerline(powerline) -> None:
"""No separator glyph to draw a chip with — the PicoCalc's console font among them."""
powerline(False)
chip = name_chip("Alice", "a1b2")
assert chip.plain == "Alice" # exactly what an arrow-mode path line makes of one hop
assert str(next(iter(chip.spans)).style) == node_style("a1b2") # still its own hue