From bcf387c620410060aeb3a221b24394fd7a9df658 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Sun, 6 Sep 2026 02:01:55 -0400 Subject: [PATCH] The test fixtures stop being a picture of one person's mesh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every node in the suite was named after hardware on JP's desk: YUL-Cartierville and YUL-Poly are repeaters he runs, two more were the BLE companions, another his own handle, and a long tail of YUL-Far / YUL-Me / bare YUL besides. A stranger reading the tests learned his callsign, his city and his gear before learning what the tests check. They join the family the suite had already invented for exactly this — Alice, Lounge, Yagi-Repeater, hub-1: Hilltop-Repeater, Lakeside, Waymarker, Homestead, and Hub for the 3d-keyed node. Hub is not a new name; test_device_config already called that contact Hub, so the suite was carrying two names for one fixture. Replacements are the same length as what they replace, so every width budget, truncation assertion and gallery line is unchanged. The two map tests that type a literal query picked `yu`/`yul` to match the old name; they type `hi`/`hil` now, same length, same single match. Left alone on purpose: the About page and the donate link, where the handle and the repeaters are the point, the hand-drawn splash art, and the XIAO README reporting what JP's own radio advertises. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- docs/survey-navigation.md | 4 +- meshterm/ui/livefeed_screen.py | 2 +- meshterm/ui/message_paths_screen.py | 2 +- meshterm/ui/path_composer.py | 2 +- meshterm/ui/specimen.py | 2 +- meshterm/ui/trace_screen.py | 2 +- meshterm/ui/tui/render.py | 2 +- meshterm/ui/walk_screen.py | 4 +- tests/test_admin_login.py | 4 +- tests/test_courier.py | 62 +++++++------- tests/test_dashboard.py | 2 +- tests/test_gallery.py | 30 +++---- tests/test_live_screens.py | 10 +-- tests/test_livefeed.py | 10 +-- tests/test_map.py | 48 +++++------ tests/test_message_paths_screen.py | 18 ++--- tests/test_navigation.py | 14 ++-- tests/test_packet_viewer.py | 10 +-- tests/test_pathgraph.py | 6 +- tests/test_pathline.py | 10 +-- tests/test_picocalc_chrome.py | 6 +- tests/test_records_screen.py | 10 +-- tests/test_repeater_admin.py | 6 +- tests/test_selection_colour.py | 26 +++--- tests/test_theme16.py | 4 +- tests/test_timemachine.py | 30 +++---- tests/test_tui.py | 4 +- tests/test_walk.py | 120 ++++++++++++++-------------- tests/test_watchtower.py | 22 ++--- tests/test_widgets.py | 10 +-- 31 files changed, 242 insertions(+), 242 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f746a9f..dd65c69 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -175,7 +175,7 @@ as a grouped list does. Filling a page in is editing its `.md`; no Python follow ### Titles -- Sentence case, always ("Trace — YUL-Poly", "Nodes", "Path width", "Admin login"). +- Sentence case, always ("Trace — Lakeside", "Nodes", "Path width", "Admin login"). - **No emoji in any screen or dialog title** — icons live in menu/list rows. (A terminal that draws an emoji narrower than Rich measures leaves a content-sized dialog's border short.) diff --git a/docs/survey-navigation.md b/docs/survey-navigation.md index 2b3a8bf..2ffe57c 100644 --- a/docs/survey-navigation.md +++ b/docs/survey-navigation.md @@ -275,8 +275,8 @@ Four screens carry a live find-as-you-type filter and draw it through the one | **Select list** | **leaves the screen, filter and all** ([`select.py:773-774`](../meshterm/ui/tui/select.py#L773)) | deletes a char | | **Path composer** | **leaves the screen, filter and all** ([`path_composer.py:717-718`](../meshterm/ui/path_composer.py#L717)) | deletes a char, then pops a hop | -So on the map, typing `yul` then Esc shows you the map again. On a contact list, typing -`yul` then Esc drops you back to the menu. Same affordance, same glyphs, opposite outcome — +So on the map, typing `hub` then Esc shows you the map again. On a contact list, typing +`hub` then Esc drops you back to the menu. Same affordance, same glyphs, opposite outcome — and the select list is by far the most-used of the four. Chat is a fifth member of the family with a different peelable thing diff --git a/meshterm/ui/livefeed_screen.py b/meshterm/ui/livefeed_screen.py index d8fc47c..fc63f0f 100644 --- a/meshterm/ui/livefeed_screen.py +++ b/meshterm/ui/livefeed_screen.py @@ -108,7 +108,7 @@ _FEED_SEED_FLOOR = datetime.min.replace(tzinfo=timezone.utc) #: The feed's fixed subject-lane width; anything longer ellipsizes so the columns hold. #: Sized for a node name, which is what most classes put here — and wide enough that the -#: two endpoints of an addressed frame still read as ``a1 → YUL-Cartierv…`` rather than +#: two endpoints of an addressed frame still read as ``a1 → Hilltop-Repe…`` rather than #: collapsing to bare hashes. Widening it would push the row past the 72-column screen #: the lanes are budgeted for, so the cells the lane can't hold are the viewer's to spend. _FEED_SUBJECT_WIDTH = 18 diff --git a/meshterm/ui/message_paths_screen.py b/meshterm/ui/message_paths_screen.py index 0d1d6d9..299370d 100644 --- a/meshterm/ui/message_paths_screen.py +++ b/meshterm/ui/message_paths_screen.py @@ -17,7 +17,7 @@ detail read together: and introduced by nothing, the **route** as a path line (:mod:`~meshterm.ui.pathline`): the origin, every relay, and us — the same two endpoints the graph draws between, so the row and the picture start and finish in the same places. Each node is a chip in its - own hue, named where we know it (``YUL-Poly``) and standing in its own hash at the + own hue, named where we know it (``Lakeside``) and standing in its own hash at the device's path-hash width where we don't (``e839f2``, keyless grey); our own end is the app-wide ``★``. No hash is repeated after a name — the graph above is where the hash bytes live, and a chip and its label share the node's colour, so the two halves diff --git a/meshterm/ui/path_composer.py b/meshterm/ui/path_composer.py index 361a643..b0826d5 100644 --- a/meshterm/ui/path_composer.py +++ b/meshterm/ui/path_composer.py @@ -455,7 +455,7 @@ class PathComposerScreen(Screen): knows no name for it. The owning screen's resolver (:func:`~meshterm.services.trace_runner.make_node_resolver`) is the same first-match lookup the trace screens name their walked hops by, so falling back - to it means a node the trace window shows as *YUL-Poly* is *YUL-Poly* here too, + to it means a node the trace window shows as *Lakeside* is *Lakeside* here too, instead of a bare ``3d`` the reader has to decode. Nothing is invented: an unmatched hash comes back unchanged and renders as a hash. diff --git a/meshterm/ui/specimen.py b/meshterm/ui/specimen.py index 4b81f3a..8c1d020 100644 --- a/meshterm/ui/specimen.py +++ b/meshterm/ui/specimen.py @@ -23,7 +23,7 @@ from .widgets import _recency_style, channel_glyph, name_chip #: Demo identities for the name-colour row: ``(name, key)``. The keys are synthetic, and #: chosen so their first bytes land in three different sectors of the hue wheel — on the #: console that means three different palette slots (see ``theme._NODE_SLOT_HEXES``). -_DEMO_NAMES = (("Alice", "a1b2"), ("YUL-Poly", "3d63"), ("Lounge", "cc10")) +_DEMO_NAMES = (("Alice", "a1b2"), ("Lakeside", "3d63"), ("Lounge", "cc10")) def _palette_row() -> Text: diff --git a/meshterm/ui/trace_screen.py b/meshterm/ui/trace_screen.py index 7b6225a..1eb16f2 100644 --- a/meshterm/ui/trace_screen.py +++ b/meshterm/ui/trace_screen.py @@ -274,7 +274,7 @@ class TracingDialog(Screen): """Build the dialog. Args: - title: Heading for the dialog border (e.g. ``Tracing — YUL-Poly``). + title: Heading for the dialog border (e.g. ``Tracing — Lakeside``). spinner: The spinner to animate (shared with the owner's ticker). on_abort: Invoked when the user asks to abort (idempotent expected). """ diff --git a/meshterm/ui/tui/render.py b/meshterm/ui/tui/render.py index 20047d2..2f71761 100644 --- a/meshterm/ui/tui/render.py +++ b/meshterm/ui/tui/render.py @@ -247,7 +247,7 @@ def render_lines(renderable: RenderableType, width: int, *, no_wrap: bool = Fals def query_line(query: str, width: int) -> str: - """The live find/filter query echoed as one body line: ``/yul``, warn-styled. + """The live find/filter query echoed as one body line: ``/hub``, warn-styled. THE way a find-as-you-type screen shows what has been typed. Every such screen draws this same line above whatever the query narrows — the select list above its rows, the diff --git a/meshterm/ui/walk_screen.py b/meshterm/ui/walk_screen.py index ed5f704..4399d82 100644 --- a/meshterm/ui/walk_screen.py +++ b/meshterm/ui/walk_screen.py @@ -36,7 +36,7 @@ only its immediate neighbourhood: **Enter walks**: the highlighted neighbour becomes the new focus, the breadcrumb trail across the top grows (a :mod:`~meshterm.ui.pathline` path line — powerline chips where the -terminal draws them, ``you › YUL-Cartierville › …`` where it doesn't, each name in its +terminal draws them, ``you › Hilltop-Repeater › …`` where it doesn't, each name in its node's own hue), and **⌫ steps back** along it. Walking to a node already on the trail truncates the stack to its first appearance — the loop you walked to get back there is dropped rather than recorded. The trail never wraps: it is one line read through a @@ -271,7 +271,7 @@ class WalkScreen(Screen): """ super().__init__() # The screen's name, and nothing else (JP, 2026-08-30). It carried the focus and - # the graph's size — ``Mesh walk — YUL-Cartierville · 42 nodes · 61 links`` — which + # the graph's size — ``Mesh walk — Hilltop-Repeater · 42 nodes · 61 links`` — which # ran past a 53-column title bar, and every atom of it was already on the screen # below: the trail ends on the focus, the line under it names that node in colour # with its hash and its ring, and how big the neighbourhood is, is what the canvas diff --git a/tests/test_admin_login.py b/tests/test_admin_login.py index 6b6656e..df6bc1e 100644 --- a/tests/test_admin_login.py +++ b/tests/test_admin_login.py @@ -367,7 +367,7 @@ def test_the_reply_budget_is_sized_to_the_route_the_login_has_to_walk() -> None: trace_runner.trace_timeout = spy try: two_hops = Contact( - name="YUL-Far", public_key="a1b2c3d4" * 8, route_hops=("3d", "f2") + name="Hub-Far", public_key="a1b2c3d4" * 8, route_hops=("3d", "f2") ) asyncio.run(_device(_FakeMeshCore()).admin_login(two_hops, "hunter2")) asyncio.run(_device(_FakeMeshCore()).admin_login(_NODE, "hunter2")) @@ -401,7 +401,7 @@ def test_a_known_short_route_never_buys_less_patience_than_no_route_at_all() -> trace_runner.trace_timeout = spy try: - near = Contact(name="YUL-Near", public_key="a1b2c3d4" * 8, route_hops=("3d",)) + near = Contact(name="Hub-Near", public_key="a1b2c3d4" * 8, route_hops=("3d",)) asyncio.run(_device(_FakeMeshCore()).admin_login(near, "hunter2")) finally: trace_runner.trace_timeout = real diff --git a/tests/test_courier.py b/tests/test_courier.py index e537888..2aafda3 100644 --- a/tests/test_courier.py +++ b/tests/test_courier.py @@ -25,7 +25,7 @@ from meshterm.ui.courier_screen import ( ) NODE = "3d" * 6 -CONTACT = Contact(name="YUL", public_key="3d" * 32) +CONTACT = Contact(name="Hub", public_key="3d" * 32) class _StubChat: @@ -73,7 +73,7 @@ def test_store_queue_round_trips_and_persists(tmp_path: Path) -> None: path = tmp_path / "courier.json" store = CourierStore(path) when = utcnow() + timedelta(hours=8) - queued = store.queue(NODE, "YUL", "hello there", not_before=when) + queued = store.queue(NODE, "Hub", "hello there", not_before=when) assert queued.ident == 1 and queued.status == QUEUED again = CourierStore(path) @@ -86,8 +86,8 @@ def test_store_queue_round_trips_and_persists(tmp_path: Path) -> None: def test_store_lifecycle_attempts_finish_cancel_clear(tmp_path: Path) -> None: """Attempt marks, delivery, cancellation, and clearing all behave.""" store = CourierStore(tmp_path / "courier.json") - a = store.queue(NODE, "YUL", "one") - b = store.queue(NODE, "YUL", "two") + a = store.queue(NODE, "Hub", "one") + b = store.queue(NODE, "Hub", "two") store.note_attempt(a.ident) assert store.get(a.ident).attempts == 1 store.mark_delivered(a.ident) @@ -102,9 +102,9 @@ def test_store_lifecycle_attempts_finish_cancel_clear(tmp_path: Path) -> None: def test_store_caps_the_finished_history(tmp_path: Path) -> None: """Old finished entries fall off; the waiting queue is never trimmed.""" store = CourierStore(tmp_path / "courier.json") - keeper = store.queue(NODE, "YUL", "still waiting") + keeper = store.queue(NODE, "Hub", "still waiting") for i in range(DONE_CAP + 5): - entry = store.queue(NODE, "YUL", f"m{i}") + entry = store.queue(NODE, "Hub", f"m{i}") store.mark_delivered(entry.ident) done = [m for m in store.entries() if m.status != QUEUED] assert len(done) == DONE_CAP @@ -120,14 +120,14 @@ def test_eligibility_waits_for_freshness_and_schedule(tmp_path: Path) -> None: store = service._ctx.courier_store now = utcnow() - plain = store.queue(NODE, "YUL", "hi") + plain = store.queue(NODE, "Hub", "hi") assert not service.eligible(plain, now) # never heard this session service._heard[NODE] = now - timedelta(seconds=FRESH_S + 1) assert not service.eligible(plain, now) # heard, but too long ago service._heard[NODE] = now assert service.eligible(plain, now) - scheduled = store.queue(NODE, "YUL", "later", not_before=now + timedelta(hours=1)) + scheduled = store.queue(NODE, "Hub", "later", not_before=now + timedelta(hours=1)) assert not service.eligible(scheduled, now) # the schedule holds it # Past its time, a scheduled entry's *first* shot fires even unheard-of. service._heard.clear() @@ -139,7 +139,7 @@ def test_eligibility_backs_off_after_failures(tmp_path: Path) -> None: service = _service(tmp_path, []) store = service._ctx.courier_store now = utcnow() - entry = store.queue(NODE, "YUL", "hi") + entry = store.queue(NODE, "Hub", "hi") service._heard[NODE] = now store.note_attempt(entry.ident, when=now) entry = store.get(entry.ident) @@ -155,11 +155,11 @@ async def test_attempt_delivers_and_raises_the_good_news(tmp_path: Path) -> None """An acknowledged send settles the entry and lights the Watchtower badge.""" service = _service(tmp_path, [True]) ctx = service._ctx - entry = ctx.courier_store.queue(NODE, "YUL", "hello") + entry = ctx.courier_store.queue(NODE, "Hub", "hello") service._heard[NODE] = utcnow() await service._pass() - assert ctx.chat.sent == [("YUL", "hello")] + assert ctx.chat.sent == [("Hub", "hello")] settled = ctx.courier_store.get(entry.ident) assert settled.status == "delivered" and settled.attempts == 1 alerts = ctx.watch_store.alerts() @@ -171,7 +171,7 @@ async def test_attempt_gives_up_after_the_budget(tmp_path: Path) -> None: """Unacknowledged attempts exhaust the budget and say so, exactly once.""" service = _service(tmp_path, [False] * MAX_ATTEMPTS) ctx = service._ctx - entry = ctx.courier_store.queue(NODE, "YUL", "hello") + entry = ctx.courier_store.queue(NODE, "Hub", "hello") for _ in range(MAX_ATTEMPTS): message = ctx.courier_store.get(entry.ident) await service._attempt(message) @@ -185,8 +185,8 @@ async def test_pass_attempts_at_most_one_entry(tmp_path: Path) -> None: """A backlog drains one message per pass — the courier never bursts.""" service = _service(tmp_path, [True, True]) ctx = service._ctx - ctx.courier_store.queue(NODE, "YUL", "first") - ctx.courier_store.queue(NODE, "YUL", "second") + ctx.courier_store.queue(NODE, "Hub", "first") + ctx.courier_store.queue(NODE, "Hub", "second") service._heard[NODE] = utcnow() await service._pass() assert len(ctx.chat.sent) == 1 @@ -212,7 +212,7 @@ async def test_attempt_now_forces_a_send(tmp_path: Path) -> None: service = _service(tmp_path, [True]) ctx = service._ctx entry = ctx.courier_store.queue( - NODE, "YUL", "hello", not_before=utcnow() + timedelta(hours=8) + NODE, "Hub", "hello", not_before=utcnow() + timedelta(hours=8) ) assert await service.attempt_now(entry.ident) == "delivered" assert await service.attempt_now(entry.ident) == "gone" # already settled @@ -265,8 +265,8 @@ async def test_cli_list_renders_waiting_and_finished(tmp_path: Path) -> None: from meshterm.tools.courier import CourierTool ctx = _ToolCtx(tmp_path) - ctx.courier_store.queue(NODE, "YUL", "still waiting") - done = ctx.courier_store.queue(NODE, "YUL", "landed") + ctx.courier_store.queue(NODE, "Hub", "still waiting") + done = ctx.courier_store.queue(NODE, "Hub", "landed") ctx.courier_store.mark_delivered(done.ident) result = await CourierTool().run(ctx, {"cli_action": "list"}) @@ -289,7 +289,7 @@ async def test_cli_cancel_removes_a_waiting_entry(tmp_path: Path) -> None: from meshterm.tools.courier import CourierTool ctx = _ToolCtx(tmp_path) - entry = ctx.courier_store.queue(NODE, "YUL", "nope") + entry = ctx.courier_store.queue(NODE, "Hub", "nope") tool = CourierTool() result = await tool.run(ctx, {"cli_action": "cancel", "id": entry.ident}) @@ -305,7 +305,7 @@ async def test_cli_send_forces_one_attempt(tmp_path: Path) -> None: from meshterm.tools.courier import CourierTool ctx = _ToolCtx(tmp_path, outcome="delivered") - entry = ctx.courier_store.queue(NODE, "YUL", "hello") + entry = ctx.courier_store.queue(NODE, "Hub", "hello") result = await CourierTool().run(ctx, {"cli_action": "send", "id": entry.ident}) assert result.summary == {"id": entry.ident, "outcome": "delivered"} @@ -325,8 +325,8 @@ async def test_cli_clear_drops_finished_only(tmp_path: Path) -> None: from meshterm.tools.courier import CourierTool ctx = _ToolCtx(tmp_path) - ctx.courier_store.queue(NODE, "YUL", "still here") - done = ctx.courier_store.queue(NODE, "YUL", "gone soon") + ctx.courier_store.queue(NODE, "Hub", "still here") + done = ctx.courier_store.queue(NODE, "Hub", "gone soon") ctx.courier_store.mark_delivered(done.ident) result = await CourierTool().run(ctx, {"cli_action": "clear"}) @@ -377,7 +377,7 @@ async def test_pick_schedule_when_next_heard_returns_no_schedule() -> None: returns on Esc, so choosing it read as a cancel and the message was never queued. """ ctx = _ScheduleCtx(_StubScheduleSession(lambda items: items[0].value)) - result = await _pick_schedule(ctx, "YUL") + result = await _pick_schedule(ctx, "Hub") assert result is None assert result is not CANCEL_SCHEDULE assert WHEN_HEARD is not None # the row's own sentinel, distinct from Esc's None @@ -386,13 +386,13 @@ async def test_pick_schedule_when_next_heard_returns_no_schedule() -> None: async def test_pick_schedule_esc_cancels_the_queueing() -> None: """Esc on the picker (``select`` returns ``None``) backs out without queueing anything.""" ctx = _ScheduleCtx(_StubScheduleSession(lambda items: None)) - assert await _pick_schedule(ctx, "YUL") is CANCEL_SCHEDULE + assert await _pick_schedule(ctx, "Hub") is CANCEL_SCHEDULE async def test_pick_schedule_a_fixed_delay_holds_until_its_time() -> None: """A concrete offset row (e.g. In 1 h) comes back as its aware future datetime.""" ctx = _ScheduleCtx(_StubScheduleSession(lambda items: items[1].value)) # In 1 h - result = await _pick_schedule(ctx, "YUL") + result = await _pick_schedule(ctx, "Hub") assert result is not None and result is not CANCEL_SCHEDULE assert result > utcnow() @@ -417,15 +417,15 @@ def test_outbox_refresh_moves_a_delivered_entry_without_a_keypress(tmp_path: Pat from meshterm.ui.courier_screen import CourierOutboxScreen ctx = _outbox_ctx(tmp_path) - entry = ctx.courier_store.queue(NODE, "YUL", "hold this") + entry = ctx.courier_store.queue(NODE, "Hub", "hold this") screen = CourierOutboxScreen(ctx) before = _outbox_plain(screen) - assert "⏳ YUL" in before and "Finished" not in before + assert "⏳ Hub" in before and "Finished" not in before ctx.courier_store.mark_delivered(entry.ident) screen.refresh() after = _outbox_plain(screen) - assert "Finished" in after and "✓ YUL" in after and "⏳" not in after + assert "Finished" in after and "✓ Hub" in after and "⏳" not in after def test_outbox_refresh_keeps_the_highlight_on_its_entry(tmp_path: Path) -> None: @@ -433,8 +433,8 @@ def test_outbox_refresh_keeps_the_highlight_on_its_entry(tmp_path: Path) -> None from meshterm.ui.courier_screen import CourierOutboxScreen ctx = _outbox_ctx(tmp_path) - first = ctx.courier_store.queue(NODE, "YUL", "first") - second = ctx.courier_store.queue(NODE, "YUL", "second") + first = ctx.courier_store.queue(NODE, "Hub", "first") + second = ctx.courier_store.queue(NODE, "Hub", "second") screen = CourierOutboxScreen(ctx, default=("msg", second.ident)) assert screen._current_choice().value == ("msg", second.ident) @@ -448,7 +448,7 @@ def test_outbox_refresh_without_change_recomposes_nothing(tmp_path: Path) -> Non from meshterm.ui.courier_screen import CourierOutboxScreen ctx = _outbox_ctx(tmp_path) - ctx.courier_store.queue(NODE, "YUL", "steady") + ctx.courier_store.queue(NODE, "Hub", "steady") screen = CourierOutboxScreen(ctx) items = screen._items screen.refresh() @@ -460,7 +460,7 @@ def test_outbox_rows_recompute_live_state_per_repaint(tmp_path: Path) -> None: from meshterm.ui.courier_screen import CourierOutboxScreen ctx = _outbox_ctx(tmp_path) - entry = ctx.courier_store.queue(NODE, "YUL", "patience") + entry = ctx.courier_store.queue(NODE, "Hub", "patience") screen = CourierOutboxScreen(ctx) assert "waiting to hear the contact" in _outbox_plain(screen) diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index e27c5ad..8268c76 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -29,7 +29,7 @@ class _FakeSession: def _screen(window=None, histogram=None, kinds=None) -> DashboardScreen: screen = DashboardScreen( session=_FakeSession(), - resolve=lambda h: {"a1b2": "Alice", "3d63": "YUL"}.get(h, ""), + resolve=lambda h: {"a1b2": "Alice", "3d63": "Hub"}.get(h, ""), window=list(window or []), activity=lambda: tuple(histogram or (0,) * ACTIVITY_BUCKETS), activity_flags=lambda: (True,) * ACTIVITY_BUCKETS, diff --git a/tests/test_gallery.py b/tests/test_gallery.py index b0e65ec..b5e9f42 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -138,7 +138,7 @@ class _Entry: def _dashboard(cols: int, rows: int) -> Screen: return DashboardScreen( session=_GallerySession(cols, rows), - resolve=lambda h: {"a1b2c3d4": "Alice", "3d63c642": "YUL-Cartierville"}.get(h, ""), + resolve=lambda h: {"a1b2c3d4": "Alice", "3d63c642": "Hilltop-Repeater"}.get(h, ""), window=[ Observation(node="a1b2c3d4", name="a1b2c3d4", kind="advert", snr=5.0, rssi=-90.0, observed_at=utcnow()), @@ -182,7 +182,7 @@ def _purge_ranked(): # noqa: ANN201 dict(heard_age_days=95.0, packets=3, known_days=200.0, hops=2.0)), ("hop-9", "9a" * 32, dict(heard_age_days=400.0, packets=1, known_days=420.0, hops=4.0)), - ("YUL-Poly", "3d" * 32, dict(heard_age_days=30.0, packets=22, known_days=250.0, + ("Lakeside", "3d" * 32, dict(heard_age_days=30.0, packets=22, known_days=250.0, hops=1.0)), ("sensor-2", "7c" * 32, dict(heard_age_days=210.0, packets=2, known_days=260.0)), ] @@ -245,14 +245,14 @@ def _node_detail_header() -> Text: # a specimen of what the platform draws, so a stub colour would hide a palette bug. glyph, glyph_style = _NODE_GLYPHS[NODE_TYPE_REPEATER] header = Text(f"{glyph} ", style=glyph_style) - header.append("YUL-Cartierville", style=name_style("YUL-Cartierville", _HUB_KEY)) + header.append("Hilltop-Repeater", style=name_style("Hilltop-Repeater", _HUB_KEY)) header.append(" repeater", style="muted") return header def _node_detail(cols: int, rows: int) -> Screen: return NodeDetailScreen( - title="Node — YUL-Cartierville", + title="Node — Hilltop-Repeater", header=_node_detail_header(), info_rows=[ ("key", highlighted_hash(_HUB_KEY, 1)), @@ -291,7 +291,7 @@ def _map(cols: int, rows: int) -> Screen: session = _GallerySession(cols, rows) markers = [ MapMarker("Homestead", 45.50, -73.60, is_self=True), - MapMarker("YUL-Cartierville", 45.40, -73.50, is_repeater=True), + MapMarker("Hilltop-Repeater", 45.40, -73.50, is_repeater=True), MapMarker("A Rather Long Node Name For Width", 45.55, -73.65), ] return MapScreen(session, markers, _StubTileSource(), 14) @@ -323,7 +323,7 @@ def _livefeed(cols: int, rows: int) -> Screen: def _walk_topo() -> tuple[MeshTopology, dict[str, Contact]]: - hub = Contact(name="YUL-Cartierville", public_key=_HUB_KEY, key_prefix="3d63c6429436") + hub = Contact(name="Hilltop-Repeater", public_key=_HUB_KEY, key_prefix="3d63c6429436") far = Contact(name="Alice", public_key=_FAR_KEY, key_prefix="f2c24f54551e") topo = MeshTopology("aa" * 6, contacts=[hub, far]) hub_id, far_id = topo.canonical(hub.public_key), topo.canonical(far.public_key) @@ -342,10 +342,10 @@ def _walk(cols: int, rows: int) -> Screen: def _timemachine(cols: int, rows: int) -> Screen: def build(window, width): # noqa: ANN001 - return [Text("YUL-Cartierville 5m ago advert"), Text("Alice 12m ago packet")] + return [Text("Hilltop-Repeater 5m ago advert"), Text("Alice 12m ago packet")] return TimeMachineScreen( - session=_GallerySession(cols, rows), label="YUL-Cartierville", build=build, + session=_GallerySession(cols, rows), label="Hilltop-Repeater", build=build, ) @@ -364,14 +364,14 @@ def _message_paths(cols: int, rows: int) -> Screen: def _remote_cli(cols: int, rows: int) -> Screen: return RemoteCliScreen( - node_label="YUL-Cartierville", - history=["get name", "get name -> YUL-Cartierville"], + node_label="Hilltop-Repeater", + history=["get name", "get name -> Hilltop-Repeater"], send=lambda c: None, session=_GallerySession(cols, rows), ) def _path_composer(cols: int, rows: int) -> Screen: - hub = Contact(name="YUL-Cartierville", public_key=_HUB_KEY, key_prefix="3d63c6429436") + hub = Contact(name="Hilltop-Repeater", public_key=_HUB_KEY, key_prefix="3d63c6429436") far = Contact(name="Alice", public_key=_FAR_KEY, key_prefix="f2c24f54551e") topo = build_topology( self_id="aaaaaaaaaaaa" + "0" * 52, contacts=[hub, far], @@ -415,7 +415,7 @@ def _courier_outbox(cols: int, rows: int) -> Screen: config_dir = Path(tempfile.mkdtemp(prefix="meshterm-gallery-courier-")) ctx = _CourierStubContext(config_dir) ctx.courier = CourierService(ctx) - ctx.courier_store.queue("aa" * 6, "YUL-Cartierville", "battery reading requested please") + ctx.courier_store.queue("aa" * 6, "Hilltop-Repeater", "battery reading requested please") ctx.courier_store.queue("bb" * 6, "A Rather Long Contact Name For Width", "hello there") return CourierOutboxScreen(ctx) @@ -439,7 +439,7 @@ def _record_dialog(cols: int, rows: int) -> Screen: record = _record() return RecordDialog( record, CATEGORY_BY_ID[record.category], 1, - resolve=lambda h: {"3d63c6429436": "YUL-Cartierville", "f2c24f54551e": "Alice"}.get(h, h), + resolve=lambda h: {"3d63c6429436": "Hilltop-Repeater", "f2c24f54551e": "Alice"}.get(h, h), device_label="Homestead", device_hash=None, ) @@ -450,7 +450,7 @@ def _packet_viewer(cols: int, rows: int) -> Screen: raw={"payload_typename": "GRP_TXT", "route_typename": "FLOOD"}, ) return PacketViewer( - [entry], 0, resolve=lambda h: {"3d63c6429436": "YUL-Cartierville"}.get(h, h), + [entry], 0, resolve=lambda h: {"3d63c6429436": "Hilltop-Repeater"}.get(h, h), ) @@ -479,7 +479,7 @@ def _trace(cols: int, rows: int) -> Screen: def _tx_sweep(cols: int, rows: int) -> Screen: screen = TxSweepScreen( - admin_label="YUL-Cartierville", target_label="Alice", device_label="Homestead", + admin_label="Hilltop-Repeater", target_label="Alice", device_label="Homestead", device_hash="00" * 32, resolve=lambda h: h, session=_GallerySession(cols, rows), tx_min=12, tx_max=28, step=3, samples=3, run_sweep=lambda: None, apply_winner=lambda: None, diff --git a/tests/test_live_screens.py b/tests/test_live_screens.py index 0ef6ae0..1e2aa15 100644 --- a/tests/test_live_screens.py +++ b/tests/test_live_screens.py @@ -706,7 +706,7 @@ def test_route_lane_wraps_at_hop_boundaries_under_its_own_column() -> None: line it continues from ends on the ``→`` cue, and every name survives whole — the lane names nodes and leaves the hex to the ``path`` lane below it. """ - names = {"3d": "YUL-Cartierville", "f2": "Mile-End-Rooftop", "27": "Beaubien-Sud"} + names = {"3d": "Hilltop-Repeater", "f2": "Mile-End-Rooftop", "27": "Beaubien-Sud"} screen, _ = _trace_screen(mode="path") screen._resolve = lambda h: names.get(h, h) screen._path_spec = "3d,f2,27" @@ -1194,7 +1194,7 @@ def test_scenario_path_leads_and_ends_with_us_and_the_target() -> None: label="observed path", hops=("3d63c6429436",), source="observed", score=1.0, ) text = _scenario_path( - scenario, topo, "f2c24f54551e", device_label="YUL-Me", width_bytes=1 + scenario, topo, "f2c24f54551e", device_label="Hub-Me", width_bytes=1 ) assert text.plain == "★ → Hub → Far" @@ -1204,7 +1204,7 @@ def test_scenario_path_direct_scenario_still_names_both_endpoints() -> None: topo = _scenario_topo() scenario = PathScenario(label="direct", hops=(), source="direct", score=0.0) text = _scenario_path( - scenario, topo, "f2c24f54551e", device_label="YUL-Me", width_bytes=1 + scenario, topo, "f2c24f54551e", device_label="Hub-Me", width_bytes=1 ) assert text.plain == "★ → Far" @@ -1223,10 +1223,10 @@ def test_scenario_path_cuts_a_long_candidate_rather_than_eliding_its_middle() -> label="observed path", hops=("3d63c6429436",), source="observed", score=1.0, ) full = _scenario_path( - scenario, topo, "f2c24f54551e", device_label="YUL-Me", width_bytes=1 + scenario, topo, "f2c24f54551e", device_label="Hub-Me", width_bytes=1 ) narrow = _scenario_path( - scenario, topo, "f2c24f54551e", device_label="YUL-Me", width_bytes=1, + scenario, topo, "f2c24f54551e", device_label="Hub-Me", width_bytes=1, width=full.cell_len - 3, ) assert narrow.cell_len <= full.cell_len - 3 diff --git a/tests/test_livefeed.py b/tests/test_livefeed.py index f76aa7b..8ae30fa 100644 --- a/tests/test_livefeed.py +++ b/tests/test_livefeed.py @@ -50,7 +50,7 @@ class _Fut: #: Nodes the feed's resolver can name, by the hash a frame addresses them with (one byte) #: and by the wider prefixes an advert carries. -_KNOWN = {"a1b2": "Alice", "3d63": "YUL", "a1": "Alice", "3d": "YUL", "c0": "Us"} +_KNOWN = {"a1b2": "Alice", "3d63": "Hub", "a1": "Alice", "3d": "Hub", "c0": "Us"} def _screen(seed=None, **kwargs) -> LiveFeedScreen: @@ -127,7 +127,7 @@ def test_livefeed_rows_leave_the_relay_path_to_the_viewer() -> None: MeshEvent.observation_event(_obs(kind="packet", path="3d63,a1b2", snr=1.0)) ) row = _rows(screen, 72)[0] - assert "via" not in row and "YUL" not in row # no route, not even a stub of one + assert "via" not in row and "Hub" not in row # no route, not even a stub of one assert "📦 packet" in row # the class still reads in words at 72 assert "+1.0 dB" in row and "-90 dBm" in row # …as does the reception it was heard at assert len(row.rstrip()) <= 72 @@ -222,14 +222,14 @@ def test_livefeed_addressed_frame_is_about_its_two_ends() -> None: # A pair too wide for the lane spends its cells on the addressee: the sender drops to # the hash it was named by rather than the recipient being the half cut off. wide = _screen() - wide._resolve = lambda h: {"a1": "Alice-With-A-Long-Name", "3d": "YUL-Cartierville"}.get(h, "") + wide._resolve = lambda h: {"a1": "Alice-With-A-Long-Name", "3d": "Hilltop-Repeater"}.get(h, "") wide.on_event( MeshEvent.observation_event( _obs(node="", kind="packet", raw={"payload_typename": "REQ", "dest_hash": "3d", "src_hash": "a1"}) ) ) - assert "a1 → YUL-Cartierv" in _rows(wide, 100)[0] + assert "a1 → Hilltop-Repe" in _rows(wide, 100)[0] def test_livefeed_tokened_classes_are_about_their_token() -> None: @@ -265,7 +265,7 @@ def test_livefeed_column_header_sits_over_the_lanes_it_names() -> None: assert stamp is not None assert _col(header, "TIME") == _col(row, stamp.group(0)) assert _col(header, "CLASS") == _col(row, "telemetry") - _ICON_LANE - assert _col(header, "SUBJECT") == _col(row, "YUL") + assert _col(header, "SUBJECT") == _col(row, "Hub") # The two readings right-align their number, so their labels end where the digits do. assert _col(header, "SNR") + 3 == _col(row, "+12.8") + 5 assert _col(header, "RSSI") + 4 == _col(row, "-105") + 4 diff --git a/tests/test_map.py b/tests/test_map.py index 908b9b4..113dcf8 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -514,7 +514,7 @@ def test_street_name_is_drawn_only_once() -> None: layers = decode_tile(_FIXTURE.read_bytes(), layers=DRAWN_LAYERS) vp = Viewport(45.5019, -73.5674, 16, 53 * 2, 22 * 4) out = _plain(render_map(vp, {(14, 4843, 5861): layers}, - [MapMarker("YUL", 45.5040, -73.5700, is_repeater=True)])) + [MapMarker("Hub", 45.5040, -73.5700, is_repeater=True)])) # René-Lévesque arrives as several segments and used to be drawn twice on one screen. assert out.count("René-Lévesque") <= 1, "a street was named more than once" @@ -1413,7 +1413,7 @@ def test_map_locate_recenters_on_our_own_node_at_the_current_zoom() -> None: from meshterm.ui.map_screen import MapScreen markers = [ - MapMarker("YUL-Cartierville", 45.53, -73.71, is_repeater=True), + MapMarker("Hilltop-Repeater", 45.53, -73.71, is_repeater=True), MapMarker("Homestead", 45.40, -73.50, is_self=True), ] screen = MapScreen(_StubSession(80, 24), markers, _StubSource(), 14) @@ -1442,32 +1442,32 @@ def test_map_echoes_the_find_query_in_the_body_only_where_the_footer_is_gone() - from meshterm.ui.map_render import MapMarker from meshterm.ui.map_screen import MapScreen - markers = [MapMarker("YUL-Cartierville", 45.53, -73.71, is_repeater=True)] + markers = [MapMarker("Hilltop-Repeater", 45.53, -73.71, is_repeater=True)] try: set_platform(REGULAR) desktop = MapScreen(_StubSession(72, 20), markers, _StubSource(), 14) assert len(desktop.render_body(72)) == 20 - for ch in "yul": + for ch in "hil": desktop.handle("text", ch) # Unchanged: the footer already shows it, so the canvas keeps the whole body. - assert "/yul" not in _plain(desktop.render_body(72)) + assert "/hil" not in _plain(desktop.render_body(72)) assert len(desktop.render_body(72)) == 20 - assert "find: yul" in desktop.footer_hint + assert "find: hil" in desktop.footer_hint set_platform(PICOCALC) device = MapScreen(_StubSession(53, 23), markers, _StubSource(), 14) assert len(device.render_body(53)) == 23 - for ch in "yul": + for ch in "hil": device.handle("text", ch) lines = device.render_body(53) # The echo overlays the canvas's *last* row — directly above the F-key lane — # so the ground never shifts: same body height, same viewport, top anchor held. - assert _plain(lines[-1]).strip() == "/yul" - assert "/yul" not in _plain(lines[0]) + assert _plain(lines[-1]).strip() == "/hil" + assert "/hil" not in _plain(lines[0]) assert len(lines) == 23 # Clearing the find hands the row back to the canvas, still without a reflow. - for _ in "yul": + for _ in "hil": device.handle("backspace") assert len(device.render_body(53)) == 23 assert "/" not in _plain(device.render_body(53)) @@ -1505,7 +1505,7 @@ def test_map_frame_chip_lights_only_with_matches_to_frame() -> None: from meshterm.ui.map_screen import MapScreen markers = [ - MapMarker("YUL-Cartierville", 45.53, -73.71, is_repeater=True), + MapMarker("Hilltop-Repeater", 45.53, -73.71, is_repeater=True), MapMarker("Alice", 45.40, -73.50), ] screen = MapScreen(_StubSession(80, 24), markers, _StubSource(), 14) @@ -1533,31 +1533,31 @@ def test_map_screen_find_filters_frames_and_clears() -> None: from meshterm.ui.map_screen import MapScreen markers = [ - MapMarker("YUL-Cartierville", 45.53, -73.71, is_repeater=True), + MapMarker("Hilltop-Repeater", 45.53, -73.71, is_repeater=True), MapMarker("Alice", 45.40, -73.50), MapMarker("Yagi-North", 45.60, -73.65), ] screen = MapScreen(_StubSession(80, 24), markers, _StubSource(), 14) screen.render_body(80) - for ch in "yu": + for ch in "hi": screen.handle("text", ch) - assert screen._filter == "yu" - assert [m.label for m in screen._matches()] == ["YUL-Cartierville"] + assert screen._filter == "hi" + assert [m.label for m in screen._matches()] == ["Hilltop-Repeater"] screen.render_body(80) assert "1 of 3 match" in screen.title - assert "find: yu" in screen.footer_hint + assert "find: hi" in screen.footer_hint # Only the match keeps a label; the others dim to bare context glyphs. body = _plain(screen.render_body(80)) - assert "YUL-Cartierville" in body + assert "Hilltop-Repeater" in body assert "Alice" not in body and "Yagi-North" not in body - # ^Enter frames the matches: the view centres on YUL, and the query survives it. + # ^Enter frames the matches: the view centres on the repeater, and the query survives. screen.handle("ctrl_enter") assert screen._viewport.center_lat == pytest.approx(45.53, abs=0.05) assert screen._viewport.center_lon == pytest.approx(-73.71, abs=0.05) - assert screen._filter == "yu" + assert screen._filter == "hi" # Plain Enter is the other way out: the query goes, the view stays exactly where the # frame left it. @@ -1567,10 +1567,10 @@ def test_map_screen_find_filters_frames_and_clears() -> None: assert (screen._viewport.center_lat, screen._viewport.center_lon) == centre # Backspace edits; Esc clears the filter first and only then dismisses. - for ch in "yu": + for ch in "hi": screen.handle("text", ch) screen.handle("backspace") - assert screen._filter == "y" + assert screen._filter == "h" async def drive() -> object: screen.future = asyncio.get_running_loop().create_future() @@ -1588,14 +1588,14 @@ def test_map_screen_frame_zooms_in_on_a_single_match() -> None: from meshterm.ui.map_screen import _FIND_ZOOM, MapScreen markers = [ - MapMarker("YUL-Cartierville", 45.53, -73.71, is_repeater=True), + MapMarker("Hilltop-Repeater", 45.53, -73.71, is_repeater=True), MapMarker("Alice", 45.40, -73.50), ] screen = MapScreen(_StubSession(80, 24), markers, _StubSource(), 19) # source max 19 screen.render_body(80) - for ch in "yul": + for ch in "hil": screen.handle("text", ch) - assert [m.label for m in screen._matches()] == ["YUL-Cartierville"] + assert [m.label for m in screen._matches()] == ["Hilltop-Repeater"] screen.handle("ctrl_enter") assert screen._viewport.center_lat == pytest.approx(45.53, abs=0.01) assert screen._viewport.center_lon == pytest.approx(-73.71, abs=0.01) diff --git a/tests/test_message_paths_screen.py b/tests/test_message_paths_screen.py index 1b6fd63..a865919 100644 --- a/tests/test_message_paths_screen.py +++ b/tests/test_message_paths_screen.py @@ -17,7 +17,7 @@ from meshterm.ui.pathline import CRACK_HEAD, CRACK_TAIL def _resolve(hop: str) -> str: - return {"3d63": "YUL-Cartierville", "a1b2": "Waymarker"}.get(hop, hop) + return {"3d63": "Hilltop-Repeater", "a1b2": "Waymarker"}.get(hop, hop) from tests.conftest import plain as _plain # THE strip-and-join screen reader @@ -53,7 +53,7 @@ def test_paths_screen_renders_graph_rows_and_cursor() -> None: assert "via" not in body # the route lane holds nothing but the route rows = body.split("sensor\n\n")[1].split("\n") assert len(rows) == 2 * len(_arrivals()) - assert rows[0].startswith("❯ ") and "YUL-Cartierville" in rows[0] # the picked route + assert rows[0].startswith("❯ ") and "Hilltop-Repeater" in rows[0] # the picked route # …with its length, time and SNR tucked beneath it, the hop count leading assert rows[1].strip().startswith("1 hop ") assert _arrivals()[0].when.astimezone().strftime("%H:%M") in rows[1] @@ -77,7 +77,7 @@ def test_paths_screen_warns_once_for_a_path_that_revisits_a_hop() -> None: ]) body = _plain(screen.render_body(76)) assert body.count("⚠") == 1 - assert "YUL-Cartierville repeats — a loop, or two nodes sharing one hash" in body + assert "Hilltop-Repeater repeats — a loop, or two nodes sharing one hash" in body graph = body.split("origin →")[0] assert graph.count("3d") == 2 # both visits marked assert graph.count("a1") == 1 # the relay the two paths *share* stays one marker @@ -97,8 +97,8 @@ def test_paths_screen_labels_every_relay_with_its_hash_byte() -> None: 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" in body and "Waymarker" in body + assert "Hilltop-Rep" not in graph and "Waymarker" not in graph + assert "Hilltop-Repeater" in body and "Waymarker" in body assert "(3d)" not in body and "(a1)" not in body # the hex lives on the graph @@ -222,7 +222,7 @@ def test_paths_screen_route_runs_origin_to_us_not_relay_to_relay() -> None: """ screen = _screen(_arrivals()) rows = _plain(screen.render_body(76)).split("sensor\n\n")[1].split("\n") - assert rows[0] == "❯ Alice → YUL-Cartierville → ★" + assert rows[0] == "❯ Alice → Hilltop-Repeater → ★" assert rows[2] == " Alice → Waymarker → 77 → ★" # an unnamed relay still stands in its hash @@ -232,9 +232,9 @@ def test_paths_screen_origin_is_a_star_for_us_and_a_question_for_nobody() -> Non now = utcnow() one = [Arrival(when=now, hops=("3d63",), snr=1.0)] ours = _plain(_screen(one, source="Homestead").render_body(76)) - assert "❯ ★ → YUL-Cartierville → ★" in ours + assert "❯ ★ → Hilltop-Repeater → ★" in ours nameless = _plain(_screen(one, source=None).render_body(76)) - assert "❯ ? → YUL-Cartierville → ★" in nameless + assert "❯ ? → Hilltop-Repeater → ★" in nameless def test_paths_screen_cuts_unselected_rows_the_same_way(monkeypatch) -> None: # noqa: ANN001 @@ -293,7 +293,7 @@ def test_an_outgoing_message_ends_on_its_recipient_not_on_us() -> None: summary="heard once", source="Homestead", destination="Bob", ) body = _plain(screen.render_body(72)) - assert "YUL-Cartierville" in body + assert "Hilltop-Repeater" in body assert body.count("Homestead") == 1, "our name belongs at one end of a send, not both" assert "Bob" in body, "the far end is the recipient" # The caption names the same far end the line ends on. diff --git a/tests/test_navigation.py b/tests/test_navigation.py index 8b7e57e..953b5bb 100644 --- a/tests/test_navigation.py +++ b/tests/test_navigation.py @@ -146,8 +146,8 @@ async def test_the_trace_target_picker_stays_pushed_under_the_live_screen(monkey from meshterm.ui.surface import TuiUi contacts = [ - Contact(name="YUL-Poly", public_key="a1" + "0" * 62, key_prefix="a1"), - Contact(name="YUL-Cartierville", public_key="3d" + "0" * 62, key_prefix="3d"), + Contact(name="Lakeside", public_key="a1" + "0" * 62, key_prefix="a1"), + Contact(name="Hilltop-Repeater", public_key="3d" + "0" * 62, key_prefix="3d"), ] class _Repo: @@ -221,10 +221,10 @@ async def test_the_tx_optimize_pickers_stay_pushed_under_the_sweep(monkeypatch) contacts = [ Contact( - name="YUL-Cartierville", public_key="3d" + "0" * 62, key_prefix="3d", + name="Hilltop-Repeater", public_key="3d" + "0" * 62, key_prefix="3d", node_type=NODE_TYPE_REPEATER, ), - Contact(name="YUL-Poly", public_key="a1" + "0" * 62, key_prefix="a1"), + Contact(name="Lakeside", public_key="a1" + "0" * 62, key_prefix="a1"), ] class _Ctx: @@ -248,9 +248,9 @@ async def test_the_tx_optimize_pickers_stay_pushed_under_the_sweep(monkeypatch) async def main() -> None: run = asyncio.ensure_future(TxOptimizeTool()._run_live(ctx)) node_list = await _screen_at(session, 1) - node_list.resolve("YUL-Cartierville") + node_list.resolve("Hilltop-Repeater") target_list = await _screen_at(session, 2) - target_list.resolve("YUL-Poly") + target_list.resolve("Lakeside") while not swept: await asyncio.sleep(0) # Esc from the sweep leaves the target list up; Esc there, the node list. @@ -261,7 +261,7 @@ async def test_the_tx_optimize_pickers_stay_pushed_under_the_sweep(monkeypatch) await asyncio.wait_for(session.run(main()), timeout=5) - assert swept == [("YUL-Cartierville", "YUL-Poly", 2)], "both pickers under the sweep" + assert swept == [("Hilltop-Repeater", "Lakeside", 2)], "both pickers under the sweep" assert session._stack == [], "and both visits pop on the way out" diff --git a/tests/test_packet_viewer.py b/tests/test_packet_viewer.py index b3552c5..2ee31a0 100644 --- a/tests/test_packet_viewer.py +++ b/tests/test_packet_viewer.py @@ -319,16 +319,16 @@ def test_packet_viewer_from_row_leads_with_the_node_type_mark() -> None: return next(l for l in _stripped(viewer.render_body(80)) if l.startswith("from")) advertised = PacketEntry( - when=utcnow(), kind="advert", node="3d63", name="YUL", node_type=NODE_TYPE_REPEATER + when=utcnow(), kind="advert", node="3d63", name="Hub", node_type=NODE_TYPE_REPEATER ) - assert "▲ YUL" in from_row(_viewer(advertised)) # the type the packet itself carried + assert "▲ Hub" in from_row(_viewer(advertised)) # the type the packet itself carried - bare = PacketEntry(when=utcnow(), kind="packet", node="3d63", name="YUL") - assert "○ YUL" in from_row(_viewer(bare)) # nothing can type it: the unknown ring + bare = PacketEntry(when=utcnow(), kind="packet", node="3d63", name="Hub") + assert "○ Hub" in from_row(_viewer(bare)) # nothing can type it: the unknown ring typed = PacketViewer( [bare], 0, resolve=lambda h: "", type_of=lambda h: NODE_TYPE_REPEATER ) - assert "▲ YUL" in from_row(typed) # …until the contacts can + assert "▲ Hub" in from_row(typed) # …until the contacts can mine = PacketEntry(when=utcnow(), kind="advert", node="3d63", name="Waymarker") ours = PacketViewer([mine], 0, resolve=lambda h: "", self_name="Waymarker") diff --git a/tests/test_pathgraph.py b/tests/test_pathgraph.py index 07eebce..e4b4acf 100644 --- a/tests/test_pathgraph.py +++ b/tests/test_pathgraph.py @@ -413,7 +413,7 @@ def test_detour_nests_outside_its_sibling_when_rows_allow() -> None: ``cc`` inserts a relay before the ``bb`` its sibling also converges through. With rows to spare the detour *nests*: ``cc`` takes the lane just outside ``bb``'s on the same flank, so the sibling's straight SRC → bb run never passes over ``cc``'s marker and the detour reads - as the wider arc through ``cc`` it really is. This is the real YUL-Poly shape: + as the wider arc through ``cc`` it really is. This is the real Lakeside shape: CDN-FENDALL1 → UpperSalaberry. """ layers = [ @@ -431,7 +431,7 @@ def test_detour_nests_outside_its_sibling_when_rows_allow() -> None: def test_detour_folds_onto_its_siblings_lane_when_rows_are_tight() -> None: """The nested lane is given up — cc rides bb's lane — only when the rows can't afford it. - The same YUL-Poly shape at a row budget too short for the nested third lane falls back to + The same Lakeside shape at a row budget too short for the nested third lane falls back to the single-lane picture: ``cc`` is re-owned onto ``bb``'s lane as a waypoint the branch dips through, and the band stays two lanes tall. The last resort, not the default. """ @@ -649,7 +649,7 @@ def test_subset_route_bypasses_the_relay_it_skips() -> None: The spine runs SRC → c1 → c2 → us with a balanced flank each side, so the endpoints ride the spine's own lane and the subset route's SRC → c2 edge is a level run straight through - ``c1``'s marker — the Furthur → YUL-Cartierville shape, where the selected route lit the + ``c1``'s marker — the Furthur → Hilltop-Repeater shape, where the selected route lit the spine's run through C14903 it never rides. The edge must bend around ``c1`` through a virtual waypoint in its column — and on the flank ``x1`` does *not* occupy, since ``x1`` shares ``c1``'s column and its lane there is taken. diff --git a/tests/test_pathline.py b/tests/test_pathline.py index 6f511e6..34305c3 100644 --- a/tests/test_pathline.py +++ b/tests/test_pathline.py @@ -66,11 +66,11 @@ 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 mesh walk trail's ``›`` — passes straight through.""" line = PathLine( - [PathHop("YUL", key="3d", annotation="3d"), PathHop("home", you=True, dim=True)], + [PathHop("Hub", key="3d", annotation="3d"), PathHop("home", you=True, dim=True)], mode="plain", ) text = line.text() - assert text.plain == "YUL (3d) → home" + assert text.plain == "Hub (3d) → home" styles = _styles(text) assert styles[" (3d)"] == "muted" # the annotation note, space included assert styles["home"] == "faint" @@ -107,7 +107,7 @@ def test_chips_are_joined_by_one_interlocked_chevron() -> None: 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)] + hops = [PathHop("Hub", 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() @@ -378,7 +378,7 @@ 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"} + names = {"aa11bb": "Alice", "3d63ab": "Hub"} hops = [None, "aa11bb", "77ccddee", "3d63ab", None] kwargs = dict( prefix_bytes=2, self_name="Me", show_hash=True, hash_bytes=3, @@ -392,7 +392,7 @@ def test_path_line_factory_matches_path_text_character_for_character() -> None: 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"} + names = {"3d63abcdef00": "Hub"} 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) diff --git a/tests/test_picocalc_chrome.py b/tests/test_picocalc_chrome.py index d4e6e12..eb2e4ae 100644 --- a/tests/test_picocalc_chrome.py +++ b/tests/test_picocalc_chrome.py @@ -125,15 +125,15 @@ def test_the_esc_hint_gives_way_before_it_crowds_the_title() -> None: Two rungs down, in order — a long title keeps the key without its verb, a longer one takes the cells back altogether. The title is what the reader came for. """ - verbless = _bar("Trace — YUL-Cartierville over a spec", "↑↓ move · Esc back") + verbless = _bar("Trace — Hilltop-Repeater over a spec", "↑↓ move · Esc back") assert verbless.rstrip().endswith(" Esc") # the verb went, the key stayed assert "Esc back" not in verbless - crowded = _bar("Trace — YUL-Cartierville over a longer spec", "↑↓ move · Esc back") + crowded = _bar("Trace — Hilltop-Repeater over a longer spec", "↑↓ move · Esc back") assert "Esc" not in crowded for bar in (verbless, crowded): - assert "YUL-Cartierville" in bar and cell_len(bar) <= 53 + assert "Hilltop-Repeater" in bar and cell_len(bar) <= 53 def test_bordered_frame_is_unchanged_on_regular() -> None: diff --git a/tests/test_records_screen.py b/tests/test_records_screen.py index b787c5b..8849c72 100644 --- a/tests/test_records_screen.py +++ b/tests/test_records_screen.py @@ -50,7 +50,7 @@ from tests.conftest import plain as _plain # THE strip-and-join screen reader def _resolve(hop: str) -> str: - return {HUB_ID: "YUL-Cartierville", FAR_ID: "Far"}.get(hop, hop) + return {HUB_ID: "Hilltop-Repeater", FAR_ID: "Far"}.get(hop, hop) def _record(**kw) -> DiscoveredPath: @@ -79,7 +79,7 @@ def test_record_dialog_shows_stats_route_and_the_trace_action() -> None: """Score in the category's unit, the resolved route bracketed by us, and actions.""" body = _plain(_dialog(_record()).render_body(60)) assert "2 nodes" in body # the score, in the discipline's own unit - assert "YUL-Cartierville" in body # a resolved relay on the route + assert "Hilltop-Repeater" in body # a resolved relay on the route assert "Homestead" in body # us, bracketing the walked route assert "Trace this path" in body # the renamed action (was "Walk again") assert "Walk again" not in body @@ -126,7 +126,7 @@ def test_record_dialog_route_runs_unlabelled_across_the_whole_card() -> None: assert route[0].startswith("★") # our end opens the walk on the app-wide star… assert route[-1].endswith("★") # …and closes it on the same assert "Homestead" not in "".join(route) # never our name, and never our key - assert "YUL-Cartierville" in "".join(route) # the hops themselves are named in full + assert "Hilltop-Repeater" in "".join(route) # the hops themselves are named in full assert len(route) > 1 # it folded rather than truncating… # …every fold hanging under the step, and the labelled lanes resume at the spec. assert all(line.startswith(" ") for line in route[1:]) @@ -202,7 +202,7 @@ def test_record_dialog_names_the_link_the_longest_leg_spanned() -> None: "km_complete": True, "leg_km": 12.4, "leg_link": [HUB_ID, FAR_ID]}, ) body = _plain(_dialog(record).render_body(60)) - assert re.search(r"longest leg\s+12\.4 km\s+YUL-Cartierville → Far", body) + assert re.search(r"longest leg\s+12\.4 km\s+Hilltop-Repeater → Far", body) def test_record_dialog_stars_our_own_end_of_the_longest_leg() -> None: @@ -216,7 +216,7 @@ def test_record_dialog_stars_our_own_end_of_the_longest_leg() -> None: line for line in _plain(_dialog(record).render_body(60)).splitlines() if "longest leg" in line ) - assert "★ → YUL-Cartierville" in lane and "Homestead" not in lane + assert "★ → Hilltop-Repeater" in lane and "Homestead" not in lane def test_record_dialog_skips_the_leg_lane_for_a_record_stored_without_one() -> None: diff --git a/tests/test_repeater_admin.py b/tests/test_repeater_admin.py index 89c2278..515e1b7 100644 --- a/tests/test_repeater_admin.py +++ b/tests/test_repeater_admin.py @@ -193,11 +193,11 @@ def test_picker_rows_hue_each_name_by_its_own_key(tui_ctx) -> None: from meshterm.ui.widgets import _NODE_GLYPHS contacts = [ - Contact(name="YUL-North", public_key="a1" * 32, key_prefix="a1b2c3d4", node_type=2), - Contact(name="YUL-South", public_key="d4" * 32, key_prefix="d4c3b2a1", node_type=2), + Contact(name="Hub-North", public_key="a1" * 32, key_prefix="a1b2c3d4", node_type=2), + Contact(name="Hub-South", public_key="d4" * 32, key_prefix="d4c3b2a1", node_type=2), ] rows, candidates = admin_picker_rows(tui_ctx, contacts) - assert [c.name for c in candidates] == ["YUL-North", "YUL-South"] + assert [c.name for c in candidates] == ["Hub-North", "Hub-South"] glyph, glyph_style = _NODE_GLYPHS[2] titles = { diff --git a/tests/test_selection_colour.py b/tests/test_selection_colour.py index 750d7b6..2de443a 100644 --- a/tests/test_selection_colour.py +++ b/tests/test_selection_colour.py @@ -48,7 +48,7 @@ def _row(*, selected: bool) -> Text: """A list row in the shape every screen builds: pointer, name, age, key lane.""" row = Text() row.append("❯ " if selected else " ", style="cursor" if selected else "") - row.append("YUL-Poly", style=name_style("YUL-Poly", _KEY)) + row.append("Lakeside", style=name_style("Lakeside", _KEY)) row.append(" ") row.append("5m", style="heat.minutes") row.append(" ") @@ -72,7 +72,7 @@ def _hues(ansi: str) -> set[str]: def test_a_selected_row_draws_its_node_name_in_the_cursor_white() -> None: """THE ask: the row you are on says "this one", not "this one is mauve".""" - hue = name_style("YUL-Poly", _KEY).removeprefix("bold ") + hue = name_style("Lakeside", _KEY).removeprefix("bold ") hues = _hues(render_to_ansi(_row(selected=True), 60)) @@ -82,7 +82,7 @@ def test_a_selected_row_draws_its_node_name_in_the_cursor_white() -> None: def test_an_unselected_row_keeps_every_name_in_its_own_hue() -> None: """The fold is the *highlight's* doing; the column below it still reads as identities.""" - hue = name_style("YUL-Poly", _KEY).removeprefix("bold ") + hue = name_style("Lakeside", _KEY).removeprefix("bold ") assert hue in _hues(render_to_ansi(_row(selected=False), 60)) @@ -140,7 +140,7 @@ def test_a_path_line_s_chips_keep_their_fills_on_the_cursor_row() -> None: """ fill = node_style(_KEY).removeprefix("bold ") row = Text(" ") - row.append(" YUL-Poly ", style=f"#0f172a on {fill}") + row.append(" Lakeside ", style=f"#0f172a on {fill}") row.style = "cursor" assert f"48;2;{int(fill[1:3], 16)};" in render_to_ansi(row, 40) @@ -155,7 +155,7 @@ def test_a_path_line_keeps_every_hop_s_hue_on_the_cursor_row() -> None: different things on the two platforms. The widget stamps its own extent and the fold spares what lies inside it. """ - hops = [PathHop("YUL-Poly", key=_KEY), PathHop("Waymarker", key="77" * 6)] + hops = [PathHop("Lakeside", key=_KEY), PathHop("Waymarker", key="77" * 6)] row = Text("❯ ", style="cursor") row.append_text(PathLine(hops, mode="plain").text()) row.style = "cursor" @@ -169,14 +169,14 @@ def test_a_path_line_keeps_every_hop_s_hue_on_the_cursor_row() -> None: def test_a_name_beside_a_path_line_still_folds() -> None: """The exception is the route, not the row: a name lane next to one is still a name.""" row = Text(" ") - row.append("YUL-Poly", style=name_style("YUL-Poly", _KEY)) + row.append("Lakeside", style=name_style("Lakeside", _KEY)) row.append(" ") row.append_text(PathLine([PathHop("Waymarker", key="77" * 6)], mode="plain").text()) row.style = "cursor" hues = _hues(render_to_ansi(row, 60)) - assert name_style("YUL-Poly", _KEY).removeprefix("bold ") not in hues # the lane folded + assert name_style("Lakeside", _KEY).removeprefix("bold ") not in hues # the lane folded assert node_style("77").removeprefix("bold ") in hues # …the route did not @@ -203,7 +203,7 @@ def test_the_message_paths_dialog_colours_its_picked_route_like_its_graph(platfo ChatMessage(text="on my way", is_channel=True, created_at=now), arrivals, matched=True, - resolve=lambda hop: {"3d63": "YUL-Cartierville", "a1b2": "Waymarker"}.get(hop, hop), + resolve=lambda hop: {"3d63": "Hilltop-Repeater", "a1b2": "Waymarker"}.get(hop, hop), prefix_bytes=1, self_name="Homestead", summary="heard twice", source="Alice", ) picked = next(line for line in screen.render_body(72) if "❯" in line) @@ -245,18 +245,18 @@ def test_the_fold_speaks_whichever_hue_vocabulary_is_bound(platform) -> None: # """PicoCalc quantizes the hue to a palette slot; the rule above it is the same rule.""" set_platform(platform) row = Text(" ") - row.append("YUL-Poly", style=name_style("YUL-Poly", _KEY)) + row.append("Lakeside", style=name_style("Lakeside", _KEY)) row.style = "cursor" lit = render_to_ansi(row, 30) - unlit = render_to_ansi(Text(" YUL-Poly"), 30) + unlit = render_to_ansi(Text(" Lakeside"), 30) assert lit != unlit - assert "YUL-Poly" in re.sub(r"\x1b\[[\d;]*m", "", lit) + assert "Lakeside" in re.sub(r"\x1b\[[\d;]*m", "", lit) if platform is PICOCALC: - assert "\x1b[1;97mYUL-Poly" in lit, lit # slot 15, the console's white + assert "\x1b[1;97mLakeside" in lit, lit # slot 15, the console's white else: - assert "38;2;255;255;255mYUL-Poly" in lit, lit + assert "38;2;255;255;255mLakeside" in lit, lit # -- the vocabulary itself ------------------------------------------------------------ diff --git a/tests/test_theme16.py b/tests/test_theme16.py index 1c7bff5..94d4fd0 100644 --- a/tests/test_theme16.py +++ b/tests/test_theme16.py @@ -222,12 +222,12 @@ def test_names_colour_by_key_on_both_platforms() -> None: """One rule everywhere: the key picks the hue, and only its first byte does.""" for platform in (REGULAR, PICOCALC): set_platform(platform) - hue = name_style("YUL-Cartierville", "3d63c6429436") + hue = name_style("Hilltop-Repeater", "3d63c6429436") assert hue.startswith("bold #"), platform.name # Any prefix of the key agrees, a rename does not move the colour, and a sender # we could not place falls to the unknown-node grey — colour is reserved for # keyed identities. - assert name_style("YUL-Cartierville", "3d") == hue + assert name_style("Hilltop-Repeater", "3d") == hue assert name_style("renamed", "3d63c6429436") == hue assert name_style("nameless", None) == "node.unknown" diff --git a/tests/test_timemachine.py b/tests/test_timemachine.py index 5dee91e..6536de1 100644 --- a/tests/test_timemachine.py +++ b/tests/test_timemachine.py @@ -48,7 +48,7 @@ def _seeded_repo(tmp_path: Path) -> Repository: repo.record_observation( run, Observation( - node=NODE, name="YUL", kind="advert", + node=NODE, name="Hub", kind="advert", snr=8.0 if hours_ago > 24 else -2.0, rssi=-95.0, observed_at=now - timedelta(hours=hours_ago), ), @@ -107,7 +107,7 @@ def test_daily_activity_counts_packets_and_nodes(tmp_path: Path) -> None: repo = _seeded_repo(tmp_path) days = repo.daily_activity() assert sum(d[1] for d in days) == 26 # 24 adverts + newcomer + the packet row - assert max(d[2] for d in days) == 2 # at most YUL + Newcomer on one day + assert max(d[2] for d in days) == 2 # at most Hub + Newcomer on one day assert days == sorted(days) # oldest first repo.close() @@ -413,7 +413,7 @@ def test_node_page_renders_all_sections(tmp_path: Path) -> None: """Volume, SNR band, rhythm, and the record roll-up all render.""" repo = _seeded_repo(tmp_path) ctx = SimpleNamespace(repo=repo) - body = _plain(_node_sections(ctx, NODE, "YUL", None, 90)) + body = _plain(_node_sections(ctx, NODE, "Hub", None, 90)) assert "Volume" in body and "24 receptions" in body assert "SNR" in body and "-2.0" in body and "+8.0" in body assert "Rhythm" in body and "10-min" in body # the finest slice 90 cols affords @@ -426,7 +426,7 @@ def test_node_page_rhythm_spans_the_day_and_shares_the_volume_gutter(tmp_path: P """The node rhythm matches the mesh's: a full-day sweep, gutter-aligned to Volume.""" repo = _seeded_repo(tmp_path) ctx = SimpleNamespace(repo=repo) - lines = _plain(_node_sections(ctx, NODE, "YUL", None, 90), width=90).split("\n") + lines = _plain(_node_sections(ctx, NODE, "Hub", None, 90), width=90).split("\n") def border_col(heading: str) -> int: start = next(i for i, line in enumerate(lines) if heading in line) @@ -929,7 +929,7 @@ def test_screen_cycles_windows_and_caches(tmp_path: Path) -> None: return [Text("page")] - screen = TimeMachineScreen(session=_FakeSession(), label="YUL", build=build) + screen = TimeMachineScreen(session=_FakeSession(), label="Hub", build=build) assert "7 d" in screen.title screen.render_body(80) screen.render_body(80) @@ -958,7 +958,7 @@ def test_picocalc_window_ring_stops_at_30_days() -> None: set_platform(PICOCALC) screen = TimeMachineScreen( - session=_FakeSession(), label="YUL", build=lambda window, width: [] + session=_FakeSession(), label="Hub", build=lambda window, width: [] ) assert "7 d" in screen.title # opens on 7 d, as everywhere screen.handle("text", "w") @@ -993,10 +993,10 @@ def _activity_repo(tmp_path: Path) -> Repository: path_hash_bytes=1, timestamp=now - timedelta(minutes=mins), ) - repo.record_trace(trun, trace("YUL-A", True, [5.0, 8.0], 40)) # min 5.0, 2 hops - repo.record_trace(trun, trace("YUL-A", True, [2.0], 30)) # min 2.0, 1 hop - repo.record_trace(trun, trace("YUL-B", True, [-3.0, 1.0, 4.0], 20)) # min -3.0, 3 hops - repo.record_trace(trun, trace("YUL-B", False, [], 15)) # timed out, no SNR + repo.record_trace(trun, trace("Hub-A", True, [5.0, 8.0], 40)) # min 5.0, 2 hops + repo.record_trace(trun, trace("Hub-A", True, [2.0], 30)) # min 2.0, 1 hop + repo.record_trace(trun, trace("Hub-B", True, [-3.0, 1.0, 4.0], 20)) # min -3.0, 3 hops + repo.record_trace(trun, trace("Hub-B", False, [], 15)) # timed out, no SNR repo.record_trace(trun, trace(PATH_TRACE_TARGET, True, [6.0], 10)) # path walk: no target crun = repo.start_run("chat", {}, None) @@ -1019,7 +1019,7 @@ def _activity_repo(tmp_path: Path) -> Repository: "INSERT INTO tx_samples " "(run_id, target, tx_power, median_min_snr, success_rate, samples, created_at) " "VALUES (?, ?, ?, ?, ?, ?, ?)", - (trun, "YUL-A", 20, 3.0, 0.8, 5, (now - timedelta(minutes=20)).isoformat()), + (trun, "Hub-A", 20, 3.0, 0.8, 5, (now - timedelta(minutes=20)).isoformat()), ) repo._conn.commit() return repo @@ -1046,7 +1046,7 @@ def test_self_trace_reach_carries_outcomes(tmp_path: Path) -> None: timed_out = [r for r in reach if not r[1]] assert len(homed) == 4 and len(timed_out) == 1 assert timed_out[0][2] is None # nothing came back to measure - assert min(r[2] for r in homed) == -3.0 # the YUL-B path's bottleneck + assert min(r[2] for r in homed) == -3.0 # the Hub-B path's bottleneck repo.close() @@ -1055,7 +1055,7 @@ def test_self_activity_ledger_tallies(tmp_path: Path) -> None: repo = _activity_repo(tmp_path) led = repo.self_activity_ledger() assert led.trace_total == 5 and led.trace_ok == 4 - assert led.trace_targets == 2 # YUL-A + YUL-B; the (path) walk names no target + assert led.trace_targets == 2 # Hub-A + Hub-B; the (path) walk names no target assert led.msg_channel == 2 and led.msg_dm == 3 assert led.dm_acked == 1 and led.dm_ackable == 2 # 2 dm sends had a verdict; 1 acked assert led.dm_peers == 2 # aa + bb @@ -1087,11 +1087,11 @@ def test_self_row_leads_the_node_list_as_a_lane() -> None: from meshterm.ui.timemachine_screen import SELF named = _lane( - ContactRow(value=SELF, name="YUL-Johputer", key="3d" * 32, you=True), + ContactRow(value=SELF, name="Homestead", key="3d" * 32, you=True), name_w=30, prefix_bytes=1, hash_w=30, ).plain assert named.startswith("★") - assert "YUL-Johputer" in named and "(you)" in named + assert "Homestead" in named and "(you)" in named assert "—" in named # heard and packets have nothing to show for us assert "3d3d" in named # our key hash, its routing prefix lit # No reachable device: a bare "you" name and a "?" hash, no "(you)" tag. diff --git a/tests/test_tui.py b/tests/test_tui.py index ab5912d..a67f236 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -2342,8 +2342,8 @@ def test_default_text_prompt_is_the_full_frame_base() -> None: assert not session._has_float() # no float — the prompt is the background assert session._base_screen() is session.top - session.top.resolve("YUL") - assert await task == "YUL" + session.top.resolve("Hub") + assert await task == "Hub" asyncio.run(main()) diff --git a/tests/test_walk.py b/tests/test_walk.py index a031a91..9d48bc9 100644 --- a/tests/test_walk.py +++ b/tests/test_walk.py @@ -18,7 +18,7 @@ from meshterm.ui.tui.render import render_to_ansi from meshterm.ui.walk_screen import _HSCROLL_STEP, WalkScreen US = "aa" * 6 -YUL = Contact(name="YUL-Cartierville", public_key="3d" * 32, node_type=2) +Hub = Contact(name="Hilltop-Repeater", public_key="3d" * 32, node_type=2) ALICE = Contact(name="Alice", public_key="b2" * 32, last_seen=utcnow() - timedelta(minutes=5)) @@ -31,13 +31,13 @@ class _FakeSession: def _topo(*, with_island: bool = False) -> MeshTopology: - """us — YUL — Alice as a two-ring chain, optionally plus a detached island pair.""" - topo = MeshTopology(US, contacts=[YUL, ALICE]) - yul = topo.canonical(YUL.public_key) + """us — Hub — Alice as a two-ring chain, optionally plus a detached island pair.""" + topo = MeshTopology(US, contacts=[Hub, ALICE]) + hub = topo.canonical(Hub.public_key) alice = topo.canonical(ALICE.public_key) when = utcnow() - topo.add_walk([topo.self_id, yul], snrs=[6.0], when=when, source="trace") - topo.add_walk([yul, alice], snrs=[-2.0], when=when, source="packet") + topo.add_walk([topo.self_id, hub], snrs=[6.0], when=when, source="trace") + topo.add_walk([hub, alice], snrs=[-2.0], when=when, source="packet") if with_island: topo.add_walk(["c3" * 6, "d4" * 6], snrs=[1.0], when=when, source="neighbour") return topo @@ -48,7 +48,7 @@ def _screen(topo: MeshTopology, cell_h: int = 24) -> WalkScreen: session=_FakeSession(), topo=topo, contacts={ - topo.canonical(YUL.public_key): YUL, + topo.canonical(Hub.public_key): Hub, topo.canonical(ALICE.public_key): ALICE, }, self_label="Homestead", @@ -69,7 +69,7 @@ def test_walk_opens_focused_on_us_with_the_link_list() -> None: body = _plain(screen.render_body(80)) assert "Homestead" in body and "this device" in body assert "Links" in body and "strongest observed first" in body - assert "YUL-Cartierville" in body # our one neighbour, as a selectable row + assert "Hilltop-Repeater" in body # our one neighbour, as a selectable row assert "edge = SNR" in body # the canvas legend # The screen's name, and nothing else: the focus, and how big the neighbourhood is, # are what the body under it is for. @@ -80,11 +80,11 @@ def test_walk_link_rows_carry_snr_evidence_and_onward_count() -> None: """A neighbour row reads SNR, samples, source tags, age, and its onward links.""" screen = _screen(_topo()) body = _plain(screen.render_body(80)) - row = next(line for line in body.split("\n") if "YUL-Cartierville" in line and "❯" in line) + row = next(line for line in body.split("\n") if "Hilltop-Repeater" in line and "❯" in line) assert "+6.0" in row # the link's median SNR assert "1×" in row # samples assert "T" in row # trace evidence tag - assert "⋯ 1" in row # one link onward (YUL — Alice) + assert "⋯ 1" in row # one link onward (Hub — Alice) assert "3d" * 6 in row # the whole 12-hex id, not a truncated prefix… assert "…" not in row # …and nothing about it elided @@ -127,7 +127,7 @@ def test_walk_graph_labels_names_in_full_when_the_canvas_has_room() -> None: long contact name keeps its letters wherever the canvas can hold it. """ long_names = [ - Contact(name="YUL-Polytechnique", public_key="e8" * 32, node_type=2), + Contact(name="Lakesidetechnique", public_key="e8" * 32, node_type=2), Contact(name="Repeater-Downtown-01", public_key="d4" * 32, node_type=2), ] topo = MeshTopology(US, contacts=long_names) @@ -145,7 +145,7 @@ def test_walk_graph_labels_names_in_full_when_the_canvas_has_room() -> None: # The canvas rows are everything above the legend line. lines = _plain(screen.render_body(80)).split("\n") canvas = "\n".join(lines[: next(i for i, l in enumerate(lines) if "edge = SNR" in l)]) - assert "YUL-Polytechnique" in canvas # 17 chars, drawn whole — no "YUL-Polytechniq…" + assert "Lakesidetechnique" in canvas # 17 chars, drawn whole — no "Lakesidetechniq…" assert "Repeater-Downtown-01" in canvas # 20 chars, whole @@ -159,23 +159,23 @@ def test_walk_selected_link_lights_the_route_that_reaches_it() -> None: from meshterm.ui.walk_screen import _snr_rgb stale = utcnow() - timedelta(days=10) # older than a week → _freshness 0.5 - yul = Contact(name="YUL", public_key="3d" * 32, node_type=2) + hub = Contact(name="Hub", public_key="3d" * 32, node_type=2) alice = Contact(name="Alice", public_key="b2" * 32) bob = Contact(name="Bob", public_key="c4" * 32) - topo = MeshTopology(US, contacts=[yul, alice, bob]) - y = topo.canonical(yul.public_key) + topo = MeshTopology(US, contacts=[hub, alice, bob]) + y = topo.canonical(hub.public_key) topo.add_walk([topo.self_id, y], snrs=[10.0], when=stale, source="trace") # approach: green topo.add_walk([y, topo.canonical(alice.public_key)], snrs=[0.0], when=stale, source="trace") # amber topo.add_walk([y, topo.canonical(bob.public_key)], snrs=[-15.0], when=stale, source="trace") # red screen = WalkScreen( session=_FakeSession(), topo=topo, - contacts={topo.canonical(c.public_key): c for c in (yul, alice, bob)}, + contacts={topo.canonical(c.public_key): c for c in (hub, alice, bob)}, self_label="Homestead", ) screen.note_viewport(24) screen.render_body(80) - screen.handle("enter") # walk to YUL — now came_from is us, the approach edge + screen.handle("enter") # walk to Hub — now came_from is us, the approach edge # Land the selection on Alice (a fan neighbour, not the ⌫-back row). while screen._rows()[screen._index] != topo.canonical(alice.public_key): screen.handle("down") @@ -212,13 +212,13 @@ def test_walk_enter_walks_and_grows_the_trail() -> None: topo = _topo() screen = _screen(topo) screen.render_body(80) - screen.handle("enter") # walk to YUL (our only neighbour) - assert screen._focus == topo.canonical(YUL.public_key) + screen.handle("enter") # walk to Hub (our only neighbour) + assert screen._focus == topo.canonical(Hub.public_key) body = _plain(screen.render_body(80)) - assert "★ › YUL-Cartierville" in body # the trail, our end on the app-wide star + assert "★ › Hilltop-Repeater" in body # the trail, our end on the app-wide star # The focus line reads name (hash) — the glyph carries the type, not a spelled-out kind. - assert "YUL-Cartierville (3d)" in body and "1 hop out" in body - assert "Alice" in body # YUL's onward neighbour is now a row + assert "Hilltop-Repeater (3d)" in body and "1 hop out" in body + assert "Alice" in body # Hub's onward neighbour is now a row # The node we walked in from is not offered back as a link — ⌫ is the way back — so # the list holds only ways *onward* and opens on the strongest of them. assert screen._rows() == [topo.canonical(ALICE.public_key)] @@ -247,7 +247,7 @@ def test_walk_the_node_walked_in_from_is_not_offered_back_as_a_link() -> None: topo = _topo() screen = _screen(topo) screen.render_body(80) - screen.handle("enter") # us → YUL + screen.handle("enter") # us → Hub assert topo.self_id not in screen._rows() assert screen._rows() == screen._fan_nodes() # the canvas draws the same set @@ -266,12 +266,12 @@ def test_walk_the_node_walked_in_from_is_not_offered_back_as_a_link() -> None: def test_walk_walking_to_an_earlier_node_drops_the_loop() -> None: """Revisiting a node already on the trail truncates the stack to its first appearance, dropping the circular stretch walked to get back there.""" - topo = MeshTopology(US, contacts=[YUL, ALICE]) - yul = topo.canonical(YUL.public_key) + topo = MeshTopology(US, contacts=[Hub, ALICE]) + hub = topo.canonical(Hub.public_key) alice = topo.canonical(ALICE.public_key) when = utcnow() - topo.add_walk([topo.self_id, yul], snrs=[6.0], when=when, source="trace") - topo.add_walk([yul, alice], snrs=[-2.0], when=when, source="packet") + topo.add_walk([topo.self_id, hub], snrs=[6.0], when=when, source="trace") + topo.add_walk([hub, alice], snrs=[-2.0], when=when, source="packet") topo.add_walk([topo.self_id, alice], snrs=[3.0], when=when, source="trace") # closes the loop screen = _screen(topo) @@ -280,11 +280,11 @@ def test_walk_walking_to_an_earlier_node_drops_the_loop() -> None: screen._index = screen._rows().index(node) screen.handle("enter") - walk_to(yul) # us → YUL - walk_to(alice) # YUL → Alice - assert screen._trail == [topo.self_id, yul, alice] + walk_to(hub) # us → Hub + walk_to(alice) # Hub → Alice + assert screen._trail == [topo.self_id, hub, alice] # Alice → us closes the loop. It is a real link onward (not the node we arrived from, - # which is YUL), so it is on offer — and taking it drops the stretch walked to get here. + # which is Hub), so it is on offer — and taking it drops the stretch walked to get here. walk_to(topo.self_id) assert screen._trail == [topo.self_id] @@ -293,10 +293,10 @@ def test_walk_trail_crops_its_head_not_its_tail_when_narrow() -> None: """A trail too long for the line is cropped at its *start*, the trophy case's crop.""" screen = _screen(_topo()) us = screen._topo.self_id - yul = screen._topo.canonical(YUL.public_key) + hub = screen._topo.canonical(Hub.public_key) alice = screen._topo.canonical(ALICE.public_key) - screen._trail = [us, yul, alice] - width = 20 # too narrow for the whole "Homestead › YUL-Cartierville › Alice" + screen._trail = [us, hub, alice] + width = 20 # too narrow for the whole "Homestead › Hilltop-Repeater › Alice" text = screen._trail_text(width).plain assert text[0] in ("…", CRACK_HEAD) # cut, not elided: no whole hop bought the mark assert "⋯" not in text @@ -346,13 +346,13 @@ def test_walk_trail_names_carry_their_node_hues() -> None: screen = _screen(_topo()) us = screen._topo.self_id - yul = screen._topo.canonical(YUL.public_key) - screen._trail = [us, yul] + hub = screen._topo.canonical(Hub.public_key) + screen._trail = [us, hub] text = screen._trail_text(80) plain = text.plain - yul_at = plain.index("YUL-Cartierville") + hub_at = plain.index("Hilltop-Repeater") assert any( - s.start <= yul_at < s.end and "bold" in str(s.style) and node_style(yul) in str(s.style) + s.start <= hub_at < s.end and "bold" in str(s.style) and node_style(hub) in str(s.style) for s in text.spans ) @@ -360,24 +360,24 @@ def test_walk_trail_names_carry_their_node_hues() -> None: def test_walk_locate_refocuses_us_and_home_end_walk_the_list() -> None: """^U resets the walk to our own node from anywhere; Home/End are the list's ends.""" # A hub of spokes off one of our neighbours: a walked-away focus with a list to move in. - topo = MeshTopology(US, contacts=[YUL]) - yul = topo.canonical(YUL.public_key) + topo = MeshTopology(US, contacts=[Hub]) + hub = topo.canonical(Hub.public_key) when = utcnow() - topo.add_walk([topo.self_id, yul], snrs=[6.0], when=when, source="trace") + topo.add_walk([topo.self_id, hub], snrs=[6.0], when=when, source="trace") for i in range(5): - topo.add_walk([yul, f"{i:02x}" * 6], snrs=[5.0 - i], when=when, source="trace") + topo.add_walk([hub, f"{i:02x}" * 6], snrs=[5.0 - i], when=when, source="trace") screen = _screen(topo) screen.render_body(80) - screen.handle("enter") # us → YUL + screen.handle("enter") # us → Hub screen.render_body(80) rows = screen._rows() assert len(rows) > 1 and topo.self_id not in rows # Home/End move the highlight — they no longer abandon the walk. screen.handle("end") - assert screen._index == len(rows) - 1 and screen._trail == [topo.self_id, yul] + assert screen._index == len(rows) - 1 and screen._trail == [topo.self_id, hub] screen.handle("home") - assert screen._index == 0 and screen._trail == [topo.self_id, yul] + assert screen._index == 0 and screen._trail == [topo.self_id, hub] screen.handle("locate") assert screen._trail == [topo.self_id] @@ -454,7 +454,7 @@ def test_walk_the_fan_is_all_east_and_holds_no_came_from() -> None: topo = _topo() screen = _screen(topo) screen.render_body(80) - screen.handle("enter") # focus YUL; we came from us + screen.handle("enter") # focus Hub; we came from us alice = topo.canonical(ALICE.public_key) fx, _fy = screen._focus_pos(80, 12) placed = screen._place_neighbours(80, 12, screen._fan_nodes(), False) @@ -736,23 +736,23 @@ def test_walk_trail_scroll_is_inert_on_a_walk_that_fits() -> None: def test_walk_find_narrows_the_canvas_fan_but_not_the_walk() -> None: """Typing thins the ways onward; the focus and the node walked from hold their place.""" bob = Contact(name="Bob-Tower", public_key="c7" * 32) - topo = MeshTopology(US, contacts=[YUL, ALICE, bob]) - yul, alice = topo.canonical(YUL.public_key), topo.canonical(ALICE.public_key) + topo = MeshTopology(US, contacts=[Hub, ALICE, bob]) + hub, alice = topo.canonical(Hub.public_key), topo.canonical(ALICE.public_key) bob_id = topo.canonical(bob.public_key) when = utcnow() - topo.add_walk([topo.self_id, yul], snrs=[6.0], when=when, source="trace") - topo.add_walk([yul, alice], snrs=[-2.0], when=when, source="packet") - topo.add_walk([yul, bob_id], snrs=[1.0], when=when, source="packet") + topo.add_walk([topo.self_id, hub], snrs=[6.0], when=when, source="trace") + topo.add_walk([hub, alice], snrs=[-2.0], when=when, source="packet") + topo.add_walk([hub, bob_id], snrs=[1.0], when=when, source="packet") screen = WalkScreen( session=_FakeSession(), topo=topo, - contacts={yul: YUL, alice: ALICE, bob_id: bob}, + contacts={hub: Hub, alice: ALICE, bob_id: bob}, self_label="Homestead", ) screen.note_viewport(24) screen.render_body(80) - screen._index = screen._rows().index(yul) - screen.handle("enter") # focus YUL, came from us + screen._index = screen._rows().index(hub) + screen.handle("enter") # focus Hub, came from us canvas = _plain(screen._canvas_lines(80, 12, None)) assert "Alice" in canvas and "Bob-Tower" in canvas # the whole fan, unfiltered @@ -763,7 +763,7 @@ def test_walk_find_narrows_the_canvas_fan_but_not_the_walk() -> None: assert "Alice" in canvas # the way onward the query is about assert "Bob-Tower" not in canvas # and the one it isn't # The focus is not a candidate: it is the walk so far, and holds through any query. - assert "YUL-Cartierville" in canvas + assert "Hilltop-Repeater" in canvas def test_walk_marks_wear_their_node_type_colour_not_the_key_hue() -> None: @@ -773,15 +773,15 @@ def test_walk_marks_wear_their_node_type_colour_not_the_key_hue() -> None: topo = _topo() screen = _screen(topo) - yul, alice = topo.canonical(YUL.public_key), topo.canonical(ALICE.public_key) + hub, alice = topo.canonical(Hub.public_key), topo.canonical(ALICE.public_key) - assert screen._glyph(yul) == (REPEATER_MARK[0], "type.repeater") + assert screen._glyph(hub) == (REPEATER_MARK[0], "type.repeater") assert screen._glyph(alice) == (NODE_MARK[0], "type.node") - assert screen._marker_rgb(yul) == mark_rgb("type.repeater") + assert screen._marker_rgb(hub) == mark_rgb("type.repeater") assert screen._marker_rgb(topo.self_id) == mark_rgb(SELF_MARK[1]) # Identity has its own lane and keeps it: the *name* is what carries the key hue. - assert screen._marker_rgb(yul) != screen._label_rgb(yul) - assert screen._label_rgb(yul) == _plain_rgb(node_style(yul)) + assert screen._marker_rgb(hub) != screen._label_rgb(hub) + assert screen._label_rgb(hub) == _plain_rgb(node_style(hub)) # The legend is a sample of those very marks, so it keys colour as well as shape. legend = screen._legend() hues = { diff --git a/tests/test_watchtower.py b/tests/test_watchtower.py index 6c54d36..000a309 100644 --- a/tests/test_watchtower.py +++ b/tests/test_watchtower.py @@ -34,7 +34,7 @@ def _service(tmp_path: Path) -> WatchtowerService: return service -def _obs(node=NODE, name="YUL", snr=None, age_s=0, kind="advert") -> Observation: +def _obs(node=NODE, name="Hub", snr=None, age_s=0, kind="advert") -> Observation: return Observation( node=node, name=name, kind=kind, snr=snr, observed_at=utcnow() - timedelta(seconds=age_s), @@ -49,13 +49,13 @@ def test_store_watch_round_trips_and_persists(tmp_path: Path) -> None: path = tmp_path / "watchtower.json" store = WatchStore(path) seen = utcnow() - timedelta(hours=2) - store.watch(NODE, "YUL", last_seen=seen) + store.watch(NODE, "Hub", last_seen=seen) store.set_silence(NODE, 24) store.set_snr_watch(NODE, False) again = WatchStore(path) entry = again.watched()[NODE] - assert entry.name == "YUL" and entry.silence_hours == 24 and not entry.snr_watch + assert entry.name == "Hub" and entry.silence_hours == 24 and not entry.snr_watch assert entry.last_heard == seen again.unwatch(NODE) assert not WatchStore(path).watched() @@ -64,8 +64,8 @@ def test_store_watch_round_trips_and_persists(tmp_path: Path) -> None: def test_store_alert_log_caps_acks_and_clears(tmp_path: Path) -> None: """Alerts append newest-first, cap, acknowledge, and clear.""" store = WatchStore(tmp_path / "watchtower.json") - first = store.add_alert("silence", "YUL", "quiet") - store.add_alert("snr", "YUL", "sagging") + first = store.add_alert("silence", "Hub", "quiet") + store.add_alert("snr", "Hub", "sagging") assert [a.kind for a in store.alerts()] == ["snr", "silence"] assert store.unacked_count() == 2 @@ -87,9 +87,9 @@ def test_store_note_heard_updates_and_refreshes_name(tmp_path: Path) -> None: store = WatchStore(tmp_path / "watchtower.json") store.watch(NODE, NODE) later = utcnow() + timedelta(minutes=5) - store.note_heard(NODE, when=later, name="YUL-Cartierville") + store.note_heard(NODE, when=later, name="Hilltop-Repeater") entry = store.watched()[NODE] - assert entry.last_heard == later and entry.name == "YUL-Cartierville" + assert entry.last_heard == later and entry.name == "Hilltop-Repeater" # --- the silence rule --------------------------------------------------------------------- @@ -99,7 +99,7 @@ def test_silence_fires_once_then_rearms_on_recovery(tmp_path: Path) -> None: """Quiet past the threshold alarms once; hearing again notes the recovery.""" service = _service(tmp_path) store = service._ctx.watch_store - store.watch(NODE, "YUL", last_seen=utcnow() - timedelta(hours=13)) + store.watch(NODE, "Hub", last_seen=utcnow() - timedelta(hours=13)) service.evaluate() # default threshold is 12 h — already past it service.evaluate() # latched: no duplicate @@ -120,7 +120,7 @@ def test_silence_respects_off_and_unheard(tmp_path: Path) -> None: """An OFF rule never alarms, however stale the mark.""" service = _service(tmp_path) store = service._ctx.watch_store - store.watch(NODE, "YUL", last_seen=utcnow() - timedelta(days=30)) + store.watch(NODE, "Hub", last_seen=utcnow() - timedelta(days=30)) store.set_silence(NODE, OFF) service.evaluate() assert store.alerts() == [] @@ -133,7 +133,7 @@ def test_snr_sag_alerts_with_cooldown(tmp_path: Path) -> None: """A clear drop in median SNR alerts once, not on every subsequent packet.""" service = _service(tmp_path) store = service._ctx.watch_store - store.watch(NODE, "YUL") + store.watch(NODE, "Hub") for _ in range(SNR_WINDOW): service.note(_obs(snr=8.0)) @@ -151,7 +151,7 @@ def test_snr_ignores_packet_rows_and_disabled_watch(tmp_path: Path) -> None: """Relay-measured packet SNR and an off switch both keep the rule quiet.""" service = _service(tmp_path) store = service._ctx.watch_store - store.watch(NODE, "YUL") + store.watch(NODE, "Hub") store.set_snr_watch(NODE, False) for snr in (9.0,) * SNR_WINDOW + (-9.0,) * SNR_WINDOW: service.note(_obs(snr=snr)) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 9690887..f0395dd 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -50,13 +50,13 @@ def test_format_ago_speaks_grammatical_prose() -> None: def _resolve(hop: str) -> str: - return {"aa": "Alice", "3d": "YUL"}.get(hop, hop) + 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 → YUL" # no parenthesized hash after a name + 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: @@ -64,7 +64,7 @@ def test_path_text_marks_us_white_and_names_in_their_hue() -> None: 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 "YUL" in styles and styles["YUL"] != "you" + assert "Hub" in styles and styles["Hub"] != "you" def test_path_text_empty_reads_as_direct() -> None: @@ -127,7 +127,7 @@ def test_path_text_dims_the_tail_from_dim_from() -> None: 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("YUL") 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 -------------------------- @@ -196,7 +196,7 @@ 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, YUL repeats — ") + assert note.plain.startswith("⚠ Alice, Hub repeats — ") assert len(note.plain) <= 72 # the footer/row budget every surface renders it inside