diff --git a/tests/test_about.py b/tests/test_about.py index ecd29f1..bf0eb8d 100644 --- a/tests/test_about.py +++ b/tests/test_about.py @@ -34,8 +34,10 @@ def _page(builder, *, viewport: int = 40, width: int = 72) -> str: def test_about_meshterm_leads_with_live_package_facts() -> None: - """The version and copyright come from the package, so the page can't describe a - build it isn't running inside. + """The About page leads with facts read live from the package. + + The version and copyright come from the package itself, so the page can't + describe a build it isn't running inside. """ text = _page(about_meshterm) @@ -140,8 +142,10 @@ def test_the_licence_section_states_the_terms_and_the_credit_it_owes() -> None: def test_the_discord_page_carries_the_invite_link_and_a_code_to_scan_it_with() -> None: - """The link is the whole page, and on a console with no clipboard the QR is how it - leaves the screen — so both have to survive the render. + """The Discord page carries both the invite link and a QR code for it. + + The link is the whole page, and on a console with no clipboard the QR is how it + leaves the screen, so both have to survive the render. """ text = _page(join_discord) @@ -184,9 +188,11 @@ def test_each_section_heading_is_a_landmark_the_page_can_pin(builder, headings) def test_the_console_lane_claims_the_section_step_only_while_the_page_scrolls() -> None: - """On the PicoCalc the lane is the only place a chord can advertise itself — but it - never advertises a key that would do nothing (``CLAUDE.md``), and a page you can see - whole has no section to step to. + """The console lane claims the section step only while the page actually scrolls. + + On the PicoCalc the lane is the only place a chord can advertise itself, but it + never advertises a key that would do nothing (``CLAUDE.md``) — and a page you can + see whole has no section to step to. """ from meshterm.platforms import PICOCALC, REGULAR, set_platform diff --git a/tests/test_advert.py b/tests/test_advert.py index 31cde97..83af39b 100644 --- a/tests/test_advert.py +++ b/tests/test_advert.py @@ -1,6 +1,8 @@ -"""Background-advert tests: the store's schedule arithmetic, the scheduler's -one-send-per-pass discipline, and the editor/executor integration that lets manual -adverts and staged cadence changes share one clock. +"""Tests for the background advert. + +The store's schedule arithmetic, the scheduler's one-send-per-pass discipline, and +the editor/executor integration that lets manual adverts and staged cadence changes +share one clock. """ from __future__ import annotations diff --git a/tests/test_braillechart.py b/tests/test_braillechart.py index 86a9bfa..5bcf142 100644 --- a/tests/test_braillechart.py +++ b/tests/test_braillechart.py @@ -258,8 +258,10 @@ def test_y_axis_labels_blanks_a_repeated_mark() -> None: def test_axis_chart_mirrors_marks_where_the_platform_affords_them() -> None: - """The desktop frames a chart with marks on both gutters; the console keeps the - right ``├`` tick but spends the mirrored label's cells on the chart instead. + """An axis chart mirrors its marks only where the platform can afford them. + + The desktop frames a chart with marks on both gutters; the console keeps the right + ``├`` tick but spends the mirrored label's cells on the chart instead. """ from meshterm.platforms import PICOCALC, set_platform from meshterm.ui.braillechart import axis_chrome diff --git a/tests/test_channels.py b/tests/test_channels.py index e88f817..7dc8c59 100644 --- a/tests/test_channels.py +++ b/tests/test_channels.py @@ -272,8 +272,10 @@ async def test_channel_capacity_tracks_a_larger_ceiling(ctx: AppContext) -> None async def test_read_channel_slots_bounds_a_never_rejecting_probe() -> None: - """On firmware that answers every slot (never rejecting an index), the configured-slot - scan stops after a run of empty slots instead of walking all CHANNEL_SLOT_PROBE_CAP. + """A probe that is never rejected still bounds itself. + + On firmware that answers every slot, the configured-slot scan stops after a run of + empty slots instead of walking all of ``CHANNEL_SLOT_PROBE_CAP``. """ class NeverRejects: diff --git a/tests/test_chat.py b/tests/test_chat.py index 6772359..a104fb6 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -515,8 +515,10 @@ def test_title_leads_with_openness_glyph(repo: Repository) -> None: def test_title_contact_dot_reflects_conversation_history(repo: Repository) -> None: - """A contact's dot fills ● once we've talked, hollow ○ before — companion pink both - ways — while the name itself carries the contact's key-derived hue. + """The title's contact dot reflects whether there is history with them. + + It fills ● once we've talked and is hollow ○ before, companion pink both ways, + while the name itself carries the contact's key-derived hue. """ from meshterm.tools.chat import _COMPANION_DOT_STYLE from meshterm.ui.theme import node_style @@ -824,8 +826,10 @@ def test_byte_counter_shows_used_over_limit_and_colors_only_used() -> None: def test_byte_counter_stays_bottom_right_when_the_compose_wraps() -> None: - """A compose line long enough to wrap keeps the counter pinned to the last line's - right edge — not trailing the cursor down onto the second row. + """The byte counter stays bottom-right when the compose line wraps. + + It stays pinned to the last line's right edge, rather than trailing the cursor + down onto the second row. """ screen = _screen(_StubSession(), send=None) text = "this is a long compose line that certainly wraps onto several rows here ok" @@ -1772,8 +1776,10 @@ async def test_picker_pins_its_column_header_over_the_group_heading( async def test_picker_del_deletes_history_after_a_red_confirm(repo: Repository) -> None: - """Del on a contacted thread: red destructive confirm, history gone, unread cleared, - and the rebuilt rows show the thread hollow (no longer deletable). + """Del on a contacted thread deletes its history, behind a red confirm. + + The confirm is the destructive one; afterwards the history is gone, unread is + cleared, and the rebuilt rows show the thread hollow and no longer deletable. """ from types import SimpleNamespace diff --git a/tests/test_config_editor.py b/tests/test_config_editor.py index 12cc509..bc7c378 100644 --- a/tests/test_config_editor.py +++ b/tests/test_config_editor.py @@ -382,8 +382,9 @@ def test_contact_share_url_defaults_to_companion_type() -> None: async def test_show_contact_card_pops_the_qr_over_the_link() -> None: - """The share popup is one floating view: a scannable QR code over the raw - ``meshcore://`` link, titled with the node's name. + """The contact card pops a QR code over its link, in one floating view. + + A scannable code above the raw ``meshcore://`` link, titled with the node's name. """ from types import SimpleNamespace diff --git a/tests/test_device_config.py b/tests/test_device_config.py index 585fdb7..c4884c0 100644 --- a/tests/test_device_config.py +++ b/tests/test_device_config.py @@ -72,8 +72,10 @@ def test_path_hash_mode_is_strict_enum() -> None: def test_highlighted_hash_highlights_path_hash_prefix() -> None: - """The full key is shown with only its path-hash prefix bytes highlighted, lit in - the node's hash-derived palette hue (the same hue the node's name wears). + """A key lane lights only its path-hash prefix. + + The full key is shown, with those bytes lit in the node's hash-derived palette + hue — the same hue the node's name wears. """ from meshterm.ui.theme import node_style from meshterm.ui.widgets import highlighted_hash @@ -88,8 +90,10 @@ def test_highlighted_hash_highlights_path_hash_prefix() -> None: def test_highlighted_hash_truncates_on_a_byte_boundary() -> None: - """A key too long for the budget keeps whole leading bytes (an even digit count) plus - an ellipsis, padding to the exact width — a half-byte digit never shows. + """A key too long for its budget is truncated on a byte boundary. + + It keeps whole leading bytes (an even digit count) plus an ellipsis, padded to the + exact width, so a half-byte digit never shows. """ from meshterm.ui.widgets import highlighted_hash @@ -250,8 +254,10 @@ def _contacts_sort(name: str = "name"): def test_contacts_screen_ctrl_arrows_steer_the_sort() -> None: - """Ctrl+←/→ walk the shared four-column ring (wrapping); Ctrl+↑/↓ force the direction — - the Time Machine picker's keys, now the Contacts screen's too. + """Ctrl+arrows steer the Contacts sort. + + Ctrl+←/→ walk the shared four-column ring, wrapping, and Ctrl+↑/↓ force the + direction — the Time Machine picker's keys, now the Contacts screen's too. """ from meshterm.ui.contacts_screen import ContactsScreen @@ -275,8 +281,10 @@ def test_contacts_screen_ctrl_arrows_steer_the_sort() -> None: def test_contacts_screen_lists_contacts_in_the_shared_lanes() -> None: - """Contacts render in the shared NAME/HEARD/PKTS/KEY lanes, our node pinned first; - plain arrows only move the highlight, and Enter resolves the highlighted node. + """Contacts are listed in the shared lanes, with our own node pinned first. + + They render in NAME/HEARD/PKTS/KEY; plain arrows only move the highlight, and + Enter resolves the highlighted node. """ import re diff --git a/tests/test_disconnect.py b/tests/test_disconnect.py index d45abc9..d570815 100644 --- a/tests/test_disconnect.py +++ b/tests/test_disconnect.py @@ -620,9 +620,11 @@ def test_serial_port_present_assumes_up_on_enumeration_error(monkeypatch) -> Non def test_serial_port_present_platform_uart_by_path(monkeypatch) -> None: - """A soldered platform UART (e.g. ``/dev/ttyS1`` on the Luckfox Lyra) is invisible to - pyserial's ``comports()`` but its ``/dev`` char-device node persists — so an existing - ``/dev`` character device reads as present, while a vanished node (a real USB unplug of + """A platform UART counts as present by its ``/dev`` node, not by a scan. + + A soldered UART (``/dev/ttyS1`` on the Luckfox Lyra) is invisible to pyserial's + ``comports()``, but its char-device node persists — so an existing character + device reads as present, while a vanished node (a real USB unplug of ``/dev/ttyUSB*``) still reads as absent. """ pytest.importorskip("serial") diff --git a/tests/test_emoji_width.py b/tests/test_emoji_width.py index 74e28ae..e73823b 100644 --- a/tests/test_emoji_width.py +++ b/tests/test_emoji_width.py @@ -135,10 +135,12 @@ def test_bare_text_default_emoji_are_left_exactly_as_measured() -> None: def test_flags_measure_two_cells_in_both_authorities() -> None: - """A country flag is a Regional Indicator pair: prompt_toolkit's wcwidth calls it four - cells, Rich and the terminal draw it as one two-cell glyph. Narrowing the indicators as a - category makes every flag sum to two in both authorities — no per-country allowlist entry, - and flags sharing an indicator (🇨🇦 / 🇨🇳) are all fixed at once. + """A country flag measures two cells in both width authorities. + + A flag is a Regional Indicator pair: prompt_toolkit's wcwidth calls it four cells, + while Rich and the terminal draw it as one two-cell glyph. Narrowing the + indicators as a category makes every flag sum to two in both — no per-country + allowlist entry, and flags sharing an indicator (🇨🇦 / 🇨🇳) are fixed at once. """ # Only the lone-emoji allowlist is passed; flags are handled by category, not by listing. cell_len = ew._make_cell_len(frozenset(_WAVE), frozenset()) @@ -192,8 +194,10 @@ def test_calibrate_width1_narrows_the_wave_in_both_authorities(monkeypatch) -> N def test_calibrate_width2_narrows_nothing_but_still_joins_clusters(monkeypatch) -> None: - """On a terminal that draws emoji two wide, narrowing would itself break the border — but - a joined sequence is over-measured at *every* width, so that one correction still lands. + """A width-2 terminal narrows nothing, but still gets the cluster join. + + Narrowing would itself break the border there — but a joined sequence is + over-measured at *every* width, so that one correction still lands. prompt_toolkit sums a ZWJ sequence's codepoints wherever it runs, which no terminal draws: the family is one two-cell glyph on the widest terminal as surely as on the narrowest. So diff --git a/tests/test_gallery.py b/tests/test_gallery.py index 611200c..2cb25b7 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -1,5 +1,7 @@ -"""The dual-platform gallery: every screen the interactive menu can open, checked for -width discipline under both flavours this app runs as. +"""The dual-platform gallery. + +Every screen the interactive menu can open, checked for width discipline under both +flavours this app runs as. Each entry below builds one full-screen :class:`~meshterm.ui.tui.screen.Screen` against hand-built (simulator-shaped) data — the same "fake session, real screen" approach every @@ -94,8 +96,10 @@ _FAR_KEY = "f2c24f54551e" + "0" * 52 class _GallerySession: - """A minimal TuiSession stand-in every gallery screen is happy with (the same shape - each screen's own test file rolls independently as ``_FakeSession``/``_StubSession``). + """A minimal TuiSession stand-in every gallery screen is happy with. + + The same shape each screen's own test file rolls independently as ``_FakeSession`` + or ``_StubSession``. """ def __init__(self, cols: int = 80, rows: int = 24) -> None: diff --git a/tests/test_live_screens.py b/tests/test_live_screens.py index a9e0caf..67213f3 100644 --- a/tests/test_live_screens.py +++ b/tests/test_live_screens.py @@ -1198,8 +1198,10 @@ def _scenario_topo() -> object: def test_scenario_path_leads_and_ends_with_us_and_the_target() -> None: - """The pathline draws the whole boomerang leg, not just the stored intermediate - hops — our own end bare, since every candidate starts from us. + """A scenario path leads and ends with us and the target. + + The pathline draws the whole boomerang leg, not just the stored intermediate hops, + with our own end bare since every candidate starts from us. """ topo = _scenario_topo() scenario = PathScenario( diff --git a/tests/test_map.py b/tests/test_map.py index 53ce051..96f51db 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -1295,9 +1295,11 @@ def test_map_tile_cache_keeps_the_absent_tile_markers() -> None: class _CapturingSession(_StubSession): - """Drives :func:`open_map` headless: captures the pushed screen, renders it once, then - replays a canned key sequence — so a test can assert on where the map opened and what - it did (or didn't) persist as the view moves. + """Drives :func:`open_map` headless, so a test can watch what it does. + + Captures the pushed screen, renders it once, then replays a canned key sequence — + which is enough to assert on where the map opened and what it did (or didn't) + persist as the view moves. """ def __init__(self, cols: int, rows: int, keys: tuple[str, ...] = ()) -> None: @@ -1487,7 +1489,9 @@ def test_map_echoes_the_find_query_in_the_body_only_where_the_footer_is_gone() - def test_map_shifted_vertical_arrows_survive_the_console_keymap(monkeypatch) -> None: - """Shift+↑ arrives as PgUp on the PicoCalc console; with Shift physically down it + """Shifted vertical arrows survive the console keymap flattening them. + + Shift+↑ arrives as PgUp on the PicoCalc console; with Shift physically down it must fine-pan, not zoom — and plain PgUp (the F-lane's zoom) stays a zoom. """ from meshterm.services import modifier_watch @@ -1646,9 +1650,11 @@ def test_map_screen_restores_and_persists_view() -> None: async def test_open_map_focus_centres_on_the_node_without_clobbering_the_saved_view( ctx, monkeypatch ) -> None: - """Opening the full map focused on a node (from its detail page) centres there, at the - inline preview's zoom — and, being a transient peek, never overwrites the persisted - 'where you left the map' global view, even as the peek is panned and zoomed. + """A focused map centres on its node without clobbering the saved view. + + Opening the full map from a node's detail page centres there at the inline + preview's zoom and, being a transient peek, never overwrites the persisted "where + you left the map" global view — even as the peek is panned and zoomed. """ from meshterm.core.geo import clamp_lat from meshterm.ui.map_render import MapMarker @@ -1679,8 +1685,9 @@ async def test_open_map_focus_centres_on_the_node_without_clobbering_the_saved_v async def test_open_map_without_focus_restores_and_persists_the_global_view( ctx, monkeypatch ) -> None: - """With no focus the full map is unchanged: it reopens where the user left it and pans - persist straight back to that global view. + """With no focus, the full map restores and persists the global view. + + It reopens where the user left it, and pans persist straight back to that view. """ from meshterm.ui.map_render import MapMarker from meshterm.ui.map_screen import open_map @@ -1752,8 +1759,10 @@ def test_map_observation_round_trips_node_type(ctx) -> None: def test_render_map_labels_take_the_name_hue_ours_white() -> None: - """Node labels carry their key-derived hue; our own label is white while the - ★ glyph stays yellow; a keyless label lands on the muted grey. + """Map labels take the node hue, and ours takes the you white. + + Our own label is white while the ★ glyph stays yellow, and a keyless label lands + on the muted grey. """ from meshterm.ui.map_render import _SELF, MapMarker, render_map from meshterm.ui.widgets import name_rgb diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 285f466..b7de18c 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -98,8 +98,10 @@ def _spans(source: str) -> list[tuple[str, str]]: [("# Title", "brand"), ("## Section", "accent"), ("### Sub", "md.strong")], ) def test_each_heading_level_takes_the_hue_its_rank_calls_for(source, style) -> None: # noqa: ANN001 - """``#`` is the page's own name, ``##`` a section in the app's body accent, ``###`` - a sub-heading inside one. + """Each heading level takes the hue its rank calls for. + + ``#`` is the page's own name, ``##`` a section in the app's body accent, and + ``###`` a sub-heading inside one. """ assert _spans(source)[0][1] == style @@ -122,8 +124,11 @@ def test_emphasis_inside_a_heading_is_its_qualifier() -> None: def test_the_page_frame_sits_flush_and_muted() -> None: - """The standfirst under the title and the colophon under the closing rule describe - the page rather than saying anything in it, so both recede and neither is indented. + """The page frame sits flush and muted. + + The standfirst under the title and the colophon under the closing rule describe + the page rather than saying anything in it, so both recede and neither is + indented. """ source = "# Title\n\nthe standfirst\n\n## Section\n\nbody\n\n---\n\nthe colophon\n" lines = _lines(source) @@ -177,7 +182,9 @@ def test_a_numbered_list_counts_from_its_own_start() -> None: def test_a_quote_keeps_its_rail_down_every_line_it_wraps_to() -> None: - """A rail that only marked the first line would make a three-line quote look like + """A quote keeps its rail down every line it wraps to. + + A rail that only marked the first line would make a three-line quote look like one quoted line followed by two loose ones. """ lines = [line for line in _lines("> " + "word " * 20, 30) if line.strip()] @@ -234,8 +241,11 @@ def test_each_inline_mark_takes_its_own_style() -> None: def test_a_link_shows_where_it_goes() -> None: - """Nothing is clickable on a framebuffer console, so a link that reads "the repo" - has to name the repo or it says nothing at all — scheme stripped, it is noise. + """A link shows where it goes. + + Nothing is clickable on a framebuffer console, so a link that reads "the repo" has + to name the repo or it says nothing at all — and with the scheme stripped, it is + noise. """ text = _texts("see [the repo](https://www.github.com/example/meshterm/)\n")[0] diff --git a/tests/test_message_paths_screen.py b/tests/test_message_paths_screen.py index 9552077..592090a 100644 --- a/tests/test_message_paths_screen.py +++ b/tests/test_message_paths_screen.py @@ -44,8 +44,9 @@ def _arrivals() -> list[Arrival]: def test_paths_screen_renders_graph_rows_and_cursor() -> None: - """The quoted text, the graph endpoints, and two lines per arrival: the route you - pick, and the reception facts hanging under it. + """The screen draws the quote, the graph, and two lines for every arrival. + + The route you pick, and the reception facts hanging under it. """ screen = _screen(_arrivals()) body = _plain(screen.render_body(76)) @@ -91,8 +92,10 @@ def test_paths_screen_stays_quiet_when_no_path_revisits() -> None: def test_paths_screen_labels_every_relay_with_its_hash_byte() -> None: - """Graph relays carry their first hash byte, both paths at once; the rows carry the - names alone — no hash repeated after one, the two tied together by the node's hue. + """Graph relays carry their first hash byte, both paths at once. + + The rows carry the names alone — no hash repeated after one, the two tied + together by the node's hue. """ screen = _screen(_arrivals()) body = _plain(screen.render_body(76)) @@ -105,8 +108,10 @@ def test_paths_screen_labels_every_relay_with_its_hash_byte() -> None: def test_paths_screen_shows_unknown_relay_as_grey_mode_width_hash() -> None: - """An unnamed relay stands in its own hash at the device's path-hash width, muted - grey — not the bare one-byte prefix, lit, and never a hash annotated onto itself. + """An unnamed relay stands in its own hash, in muted grey. + + It is drawn at the device's path-hash width — not the bare one-byte prefix, lit, + and never a hash annotated onto itself. """ now = utcnow() arrivals = [ @@ -192,9 +197,10 @@ def test_paths_screen_scrolls_the_selected_route_sideways() -> None: def test_paths_screen_cracks_the_chips_the_scroll_cuts(monkeypatch) -> None: # noqa: ANN001 - """Where the terminal draws chips, each edge the route runs past breaks the chip off - on a half block in its own colour rather than behind an ellipsis: the row is sliding - over a route that continues, not shortening a word. + """Where the terminal draws chips, the scroll cracks them rather than ellipsizing. + + Each edge the route runs past breaks the chip off on a half block in its own + colour: the row is sliding over a route that continues, not shortening a word. """ monkeypatch.setattr(pathline, "powerline_enabled", lambda: True) now = utcnow() @@ -231,8 +237,11 @@ def test_paths_screen_route_runs_origin_to_us_not_relay_to_relay() -> None: def test_paths_screen_origin_is_a_star_for_us_and_a_question_for_nobody() -> None: - """The head is named exactly as the graph's left endpoint is: our own ``★`` on a - message we sent, a bare ``?`` where the frame named nobody — never a guessed name. + """The origin is a ``★`` for us and a ``?`` for nobody. + + The head is named exactly as the graph's left endpoint is: our own star on a + message we sent, a bare question mark where the frame named nobody — never a + guessed name. """ now = utcnow() one = [Arrival(when=now, hops=("3d63",), snr=1.0)] @@ -243,8 +252,10 @@ def test_paths_screen_origin_is_a_star_for_us_and_a_question_for_nobody() -> Non def test_paths_screen_cuts_unselected_rows_the_same_way(monkeypatch) -> None: # noqa: ANN001 - """A row you are not on runs off the lane exactly as the selected one does, so it owes - the reader the same cracked chip — it just cannot slide to read the rest. + """An unselected row is cut the same way the selected one is. + + It runs off the lane exactly as the selected row does, so it owes the reader the + same cracked chip — it just cannot slide to read the rest. """ monkeypatch.setattr(pathline, "powerline_enabled", lambda: True) now = utcnow() @@ -261,8 +272,9 @@ def test_paths_screen_cuts_unselected_rows_the_same_way(monkeypatch) -> None: # def test_paths_screen_direct_arrival_and_empty_state() -> None: - """A hop-less arrival draws its two endpoints and nothing between; no arrivals at all - explain themselves. + """A hop-less arrival draws its two endpoints and nothing between. + + With no arrivals at all, the screen explains itself instead. The route no longer collapses to the word *direct*: with both ends on the line, ``Alice → ★`` **is** what a direct delivery looks like, and it reads on the same rails @@ -393,8 +405,10 @@ def test_a_short_list_advertises_no_paging() -> None: def test_the_selection_clamps_at_both_ends() -> None: - """↑ on the first arrival and ↓ on the last stay put: the window follows the highlight, - so a wrap would haul the whole list end to end instead of moving one row. + """↑ on the first arrival and ↓ on the last stay put. + + The window follows the highlight, so a wrap would haul the whole list end to end + instead of moving one row. """ screen = _screen(_many(6)) screen.note_viewport(24) diff --git a/tests/test_modifier_watch.py b/tests/test_modifier_watch.py index b5e49f9..9fc44c5 100644 --- a/tests/test_modifier_watch.py +++ b/tests/test_modifier_watch.py @@ -22,8 +22,11 @@ def test_shift_down_reflects_the_raw_flag() -> None: def test_shift_bank_key_latches_over_the_release_flicker(monkeypatch) -> None: - """An F6-F10 press keeps the lane's shifted read for a grace window, even if the raw - watcher (wrongly, per the MCU quirk) reports Shift already back up. + """A shift-bank key latches over the release flicker. + + An F6-F10 press keeps the lane's shifted read for a grace window, even if the raw + watcher reports Shift already back up — which, per the MCU quirk, it may do + wrongly. """ _reset() try: diff --git a/tests/test_node_detail.py b/tests/test_node_detail.py index d5ca81d..4dc4638 100644 --- a/tests/test_node_detail.py +++ b/tests/test_node_detail.py @@ -168,8 +168,10 @@ def test_tab_strip_labels_hold_position_across_the_active_index() -> None: def test_tab_strip_rule_fills_out_to_the_render_width() -> None: - """The bottom rule extends to the full render width; unlike the tabs, it isn't indented - by the left margin — that margin is itself part of the one continuous line. + """The tab strip's bottom rule runs the full render width. + + Unlike the tabs, it isn't indented by the left margin — that margin is itself part + of the one continuous line. """ top, _, bot = tab_strip(["Map", "Routes"], 1, width=25).renderables assert top.plain.startswith(" ╭") # the tab boxes sit indented... @@ -193,8 +195,10 @@ def test_tab_strip_collapses_a_lone_tab_to_a_plain_heading() -> None: def test_route_line_names_its_hops() -> None: - """The pathline runs contact → relays → us, every hop it can place by *name* — the - Message paths reading; the tag trails on the context line. + """A route line names every hop it can place. + + The pathline runs contact → relays → us, which is the Message paths reading; the + tag trails on the context line. """ path, context = _route_line( "Far", FAR.public_key, ("3d63c6429436",), "device", None, 0, @@ -213,8 +217,10 @@ def test_route_line_names_its_hops() -> None: def test_route_line_greys_hops_nobody_can_name() -> None: - """A named hop takes its key-derived hue; a hop nobody can name — and a page node with no - name — stands in its hash in the unknown-node grey, like the graph. + """A hop nobody can name stands in its hash, in the unknown-node grey. + + A named hop takes its key-derived hue instead; a page node with no name reads the + same as any other unnamed one, exactly as the graph draws it. """ from meshterm.ui.theme import node_style @@ -232,8 +238,10 @@ def test_route_line_greys_hops_nobody_can_name() -> None: def test_route_line_hash_width_follows_our_path_hash_mode() -> None: - """Only the hops with no name to show spend hash cells — and those at the device's own - path-hash width, three bytes here rather than the 1-byte default. + """A hash lane is as wide as the device's own path-hash mode says. + + Only the hops with no name to show spend hash cells, and they spend them at that + width — three bytes here, rather than the 1-byte default. """ path, _context = _route_line( "f2c24f54551e", FAR.public_key, ("3d63c6429436", "abcd1234ef56"), "", None, 0, @@ -332,9 +340,11 @@ def test_routes_view_puts_the_contact_on_the_left_and_us_on_the_right() -> None: def test_routes_view_tags_every_relay_with_its_hash_byte_alone() -> None: - """Only the two endpoints are named in the graph — a relay wears its first hash byte - whether or not a contact can name it, so a label never outgrows two cells and spills - across the lanes it sits between (the Message paths graph's rule exactly). + """In the graph, a relay wears its first hash byte and nothing else. + + Only the two endpoints are named, whether or not a contact could name the relay, so + a label never outgrows two cells and spills across the lanes it sits between — the + Message paths graph's rule exactly. """ topo = _topo_with_route() view = _view(topo, topo.suggested("f2c24f54551e"), ("3d63c6429436",), @@ -408,8 +418,9 @@ def _topo_two_alternatives(): # noqa: ANN202 def test_good_alternatives_keeps_observed_routes_and_drops_outliers() -> None: - """The grey alternatives are the observed routes worth trusting — outliers and the bare - direct/device families don't earn a lane. + """The grey alternatives are the observed routes worth trusting. + + Outliers and the bare direct/device families don't earn a lane. """ from meshterm.ui.node_detail_screen import _good_alternatives @@ -445,8 +456,10 @@ def test_route_freshness_drops_a_route_with_a_long_quiet_hop() -> None: def test_contract_bidir_clusters_folds_a_knot_but_keeps_the_rows() -> None: - """A 3+ bidirectional knot contracts to one super-node in the graph draw, while each route - row and its trace spec keep every member named in order. + """A 3+ bidirectional knot contracts to one super-node in the graph. + + The rows are untouched: each route row and its trace spec keep every member named, + in order. """ from meshterm.ui.node_detail_screen import _contract_bidir_clusters @@ -476,7 +489,9 @@ def test_contract_leaves_a_two_node_pair_and_unclustered_routes_alone() -> None: def test_contract_labels_a_mixed_cluster_generically() -> None: - """A knot whose members are different node types can't wear one type mark — it reads + """A mixed-type knot is labelled generically. + + Its members are different node types, so it can't wear one type mark: it reads ``n nodes`` under the plain dot. """ from meshterm.ui.node_detail_screen import _contract_bidir_clusters @@ -662,8 +677,10 @@ def test_node_detail_screen_route_selection_arms_the_trace() -> None: def test_node_detail_routes_stage_spends_the_caption_and_top_air_on_the_fan() -> None: - """On the PicoCalc the fan opens directly under the tab strip and runs uncaptioned, and - both reclaimed rows go back into the graph — never into a taller gap (JP, 2026-08-09). + """On the PicoCalc the Routes stage spends the caption and the top air on the fan. + + The fan opens directly under the tab strip and runs uncaptioned, and both reclaimed + rows go back into the graph — never into a taller gap (JP, 2026-08-09). The graph's own end padding leaves a whole empty canvas row above its topmost label; the stage draws one row over its ceiling and peels that row off, so the peel pays for itself. @@ -739,8 +756,9 @@ def test_node_detail_screen_context_hangs_under_the_pathline() -> None: def test_node_detail_screen_context_line_absent_when_theres_nothing_to_show() -> None: - """A route with no weakest SNR, sample count, or tag draws just its pathline — no bare - hanging line under it. + """A route with nothing to say draws just its pathline. + + No weakest SNR, sample count, or tag means no bare hanging line under it. """ routes = _RoutesView( routes=[_Route(draw=(), spec="", path=Text("f2 aa"), context=Text(""))], @@ -759,8 +777,10 @@ def test_node_detail_screen_context_line_absent_when_theres_nothing_to_show() -> def test_node_detail_screen_hscrolls_the_selected_pathline() -> None: - """A pathline too wide for the lane scrolls with ←/→ on the highlighted row only; moving - the cursor off it abandons the scroll, and an unselected long row just ellipsizes. + """A pathline too wide for the lane scrolls with ←/→, on the highlighted row only. + + Moving the cursor off it abandons the scroll, and an unselected long row just + ellipsizes. """ long_path = Text("f2 " + " ".join(f"{i:02x}" for i in range(40)) + " aa") routes = _RoutesView( @@ -816,10 +836,11 @@ def test_node_detail_screen_hscrolls_the_selected_pathline() -> None: def test_node_detail_route_row_cracks_a_chip_path_at_both_edges() -> None: - """The same window, drawn in chips: each edge the route runs past breaks the chip off - on a half block instead of hiding it behind an ellipsis — the row is a view onto a - route that continues, and a cracked segment says so where three dots would claim a - shortened word. + """In chips, a route row cracks at both edges rather than ellipsizing. + + Each edge the route runs past breaks the chip off on a half block: the row is a + view onto a route that continues, and a cracked segment says so where three dots + would claim a shortened word. """ chips = PathLine( [PathHop(f"NODE{i:02d}", key=f"{i:02x}aa") for i in range(12)], mode="powerline" @@ -905,8 +926,10 @@ def _key_row(screen: NodeDetailScreen, width: int) -> str: def test_node_detail_key_lane_hscrolls_instead_of_wrapping() -> None: - """The key holds one line and slides under ←/→: byte-aligned windows, faint ``…`` marks - at whichever edge it continues past, clamped at both ends. + """The key lane holds one line and slides under ←/→ rather than wrapping. + + Byte-aligned windows, faint ``…`` marks at whichever edge it continues past, and + clamped at both ends. """ screen = _screen(info_rows=[("key", highlighted_hash(_LONG_KEY, 1)), ("heard", Text("5m ago"))]) @@ -932,8 +955,10 @@ def test_node_detail_key_lane_hscrolls_instead_of_wrapping() -> None: def test_node_detail_key_lane_scroll_survives_the_cursor_and_resets_on_tab() -> None: - """The key is pinned chrome, not a cursor row: walking the action rows leaves its scroll - alone, while leaving the tab drops it — and a key that fits earns no ←→ at all. + """The key is pinned chrome, not a cursor row. + + Walking the action rows leaves its scroll alone, while leaving the tab drops it — + and a key that fits earns no ←→ at all. """ screen = _screen(info_rows=[("key", highlighted_hash(_LONG_KEY, 1)), ("heard", Text("5m ago"))]) _key_row(screen, 53) @@ -1000,8 +1025,10 @@ def test_route_labels_light_through_a_coalesced_hop() -> None: def test_node_detail_route_list_windows_inside_the_page() -> None: - """With more routes than fit, the list windows with edge markers — the pinned chrome - never leaves the screen, however many routes a busy node has. + """The route list windows inside the page rather than growing it. + + With more routes than fit, edge markers appear and the pinned chrome stays on + screen, however many routes a busy node has. """ routes = _RoutesView( routes=[ @@ -1033,8 +1060,10 @@ def test_node_detail_route_list_windows_inside_the_page() -> None: def test_node_detail_route_cursor_clamps_at_both_ends() -> None: - """The windowed route list does not wrap: ↑ on the first row and ↓ past the last stay - where they are, rather than hauling the window end to end. + """The windowed route list does not wrap. + + ↑ on the first row and ↓ past the last stay where they are, rather than hauling + the window end to end. """ routes = _RoutesView( routes=[ @@ -1059,8 +1088,10 @@ def test_node_detail_route_cursor_clamps_at_both_ends() -> None: def test_fit_blocks_walks_wrapped_rows_into_view() -> None: - """The variable-height fit keeps whole blocks, spends marker lines only when rows hide, - and walks the window down to the cursor's row. + """The variable-height fit walks wrapped rows into view. + + It keeps whole blocks, spends marker lines only when rows are actually hidden, and + walks the window down to the cursor's row. """ window = ListWindow() top, count = window.fit_blocks([2, 2, 2, 2], 5, 3) # cursor on the last 2-line row diff --git a/tests/test_pathgraph.py b/tests/test_pathgraph.py index 672b5d6..3fbcbd0 100644 --- a/tests/test_pathgraph.py +++ b/tests/test_pathgraph.py @@ -80,8 +80,10 @@ def test_identical_paths_collapse_to_the_top_layer() -> None: def test_emphasis_moves_the_highlight_not_the_layout() -> None: - """Emphasis re-colours the drawn fan on top of a fixed geometry: the same layout, painted - in different colours, so a caller can light a different route without it reflowing. + """Emphasis moves the highlight, never the layout. + + It re-colours the drawn fan on top of a fixed geometry — the same layout, painted + differently — so a caller can light another route without it reflowing. """ # A two-route fan whose spine (priority) is pinned to route 0. Only emphasis differs # between the two renders — were emphasis to drive geometry (as raising priority would), @@ -102,8 +104,10 @@ def test_emphasis_moves_the_highlight_not_the_layout() -> None: def test_bidir_clusters_finds_knots_not_pairs() -> None: - """Three-plus mutually-bidirectional nodes are a cluster to contract; a tidy two-way pair - (and any lone node) is not. + """Bidirectional clustering finds knots, not pairs. + + Three or more mutually-bidirectional nodes are a cluster to contract; a tidy + two-way pair, and any lone node, is not. """ from meshterm.ui.pathgraph import bidir_clusters @@ -119,8 +123,10 @@ def test_bidir_clusters_finds_knots_not_pairs() -> None: def test_emphasis_wins_a_shared_edge_over_a_higher_priority_spine() -> None: - """A highlighted alternative paints its whole run — even the edge it shares with the spine — - rather than dropping out where the higher-priority spine would otherwise own the colour. + """On a shared edge, emphasis beats a higher-priority spine. + + A highlighted alternative paints its whole run, including the edge it shares, + rather than dropping out where the spine would otherwise own the colour. """ # Both routes leave SRC through the same first relay (aa), so they share the SRC–aa edge; # the spine (priority 2, grey) would win that shared edge by priority, but the emphasised @@ -830,8 +836,10 @@ def test_the_emphasised_path_is_the_only_one_drawn_in_colour() -> None: def test_a_fan_with_nothing_emphasised_fades_nothing() -> None: - """No emphasis is no selection: with every layer equal there is no route to recede from, - and the caller's colours are used exactly as given. + """A fan with nothing emphasised fades nothing. + + No emphasis is no selection: with every layer equal there is no route to recede + from, and the caller's colours are used exactly as given. """ layers = [ PathLayer(hops=("aa",), color=WHITE, priority=2), @@ -856,8 +864,10 @@ def test_a_lone_path_is_never_faded() -> None: def test_the_highlight_lights_a_relay_reached_by_a_short_hash() -> None: - """Membership is decided in the graph's own id space, after the prefix coalesce: a route - that names a relay ``3d`` still lights the wide marker its hop is folded into. + """The highlight lights a relay a route reached by a short hash. + + Membership is decided in the graph's own id space, after the prefix coalesce, so a + route naming a relay ``3d`` still lights the wide marker its hop folded into. """ layers = [ PathLayer(hops=("3d63c6429436",), color=GREY, priority=2), diff --git a/tests/test_pathline.py b/tests/test_pathline.py index a3020df..e4d52d4 100644 --- a/tests/test_pathline.py +++ b/tests/test_pathline.py @@ -61,8 +61,9 @@ def _char_styles(text) -> list[tuple[str, str]]: # noqa: ANN001 def test_plain_mode_matches_the_app_wide_arrow_presentation() -> None: - """Names in their key hue, us white, keyless muted, arrows muted — the - ``path_text`` look, reproduced exactly. + """Plain mode reproduces the app-wide ``path_text`` look exactly. + + Names in their key hue, us white, keyless muted, arrows muted. """ line = PathLine( [ @@ -81,7 +82,9 @@ def test_plain_mode_matches_the_app_wide_arrow_presentation() -> None: 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; + """Annotations, dimming, and a caller's own separator all survive plain mode. + + 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( @@ -109,9 +112,11 @@ def test_empty_path_reads_as_the_callers_word() -> None: def test_chips_are_joined_by_one_interlocked_chevron() -> None: - """A seam is one cell, not two: the previous chip's point laid *on* the next chip's - fill, so the route reads as a ribbon whose segments meet on a chevron. The closing - edge keeps its lone taper — nothing follows it to lay the point on. + """A seam is one cell, not two. + + The previous chip's point is laid *on* the next chip's fill, so the route reads as + a ribbon whose segments meet on a chevron. The closing edge keeps its lone taper — + nothing follows it to lay the point on. """ alice_fill = node_style("aa").split()[-1] line = PathLine( @@ -125,8 +130,9 @@ 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. + """Chips change colours and separators, never the words. + + An explicit style override (hex or theme name) becomes the chip fill. """ hops = [PathHop("Hub", key="3d", annotation="3d"), PathHop("you", you=True)] plain = PathLine(hops, mode="plain").text().plain @@ -148,8 +154,9 @@ def test_auto_mode_follows_the_terminal_verdict(monkeypatch: pytest.MonkeyPatch) def test_the_cursor_is_a_hop_of_its_own_in_either_mode() -> None: - """The composer's insertion point is a slot *in* the route, not a gap between two - hops: it renders as a hop, so chips stay chips and arrows stay arrows. + """The composer's insertion point is a slot *in* the route, not a gap between hops. + + It renders as a hop, so chips stay chips and arrows stay arrows. """ hops = [PathHop("a"), PathHop(CURSOR_GLYPH, cursor=True), PathHop("b")] plain = PathLine(hops, mode="plain").text() @@ -163,8 +170,10 @@ def test_the_cursor_is_a_hop_of_its_own_in_either_mode() -> None: def test_ellipsized_elides_the_middle_and_keeps_both_endpoints() -> None: - """A route reads origin and destination first, so the fit drops middle hops - behind ``⋯`` — never the tail a right-edge truncation would amputate. + """The fit drops middle hops behind ``⋯`` and keeps both endpoints. + + A route reads origin and destination first, so the tail a right-edge truncation + would amputate is exactly what must survive. """ hops = [PathHop(label) for label in ("AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF")] line = PathLine(hops, mode="plain") @@ -177,8 +186,10 @@ def test_ellipsized_elides_the_middle_and_keeps_both_endpoints() -> None: def test_ellipsized_eats_the_head_when_told_to() -> None: - """A breadcrumb's news is where the walk *is*, so ``ELIDE_HEAD`` spares the origin - nothing: the fit keeps as much of the tail as the width holds, behind a ``⋯``. + """``ELIDE_HEAD`` spares the origin nothing and keeps the tail instead. + + A breadcrumb's news is where the walk *is*, so the fit keeps as much of the tail + as the width holds, behind a ``⋯``. """ hops = [PathHop(label) for label in ("AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF")] line = PathLine(hops, mode="plain") @@ -208,9 +219,10 @@ def _chips() -> PathLine: def test_cut_to_cracks_a_chip_in_its_own_fill_instead_of_ellipsizing() -> None: - """A chip caught by the cut breaks off on the half block, coloured by the very chip - it shears — the crack reads as a segment that continues, where ``…`` would claim a - word was shortened. + """A chip caught by the cut breaks off on a half block in its own fill. + + The crack reads as a segment that continues, where ``…`` would claim a word was + shortened. """ full = _chips().text() fitted = cut_to(full, 14) @@ -223,8 +235,10 @@ def test_cut_to_cracks_a_chip_in_its_own_fill_instead_of_ellipsizing() -> None: def test_cut_to_leaves_arrow_lines_on_the_ellipsis() -> None: - """Only chips crack: an arrow line has no fill to shear, so shortening it is exactly - what happened and the classic mark still says so. + """Only chips crack; an arrow line keeps the ellipsis. + + It has no fill to shear, so shortening is exactly what happened, and the classic + mark still says so. """ fitted = cut_to(PathLine(_chips().hops, mode="plain").text(), 10) assert fitted.cell_len <= 10 @@ -233,8 +247,9 @@ def test_cut_to_leaves_arrow_lines_on_the_ellipsis() -> None: def test_cut_mark_mirrors_itself_and_reads_the_visible_side() -> None: - """The head mark is the tail's mirror, and each takes the fill of the nearest cell - still *drawn* — scanning back for a tail cut, forward for a head cut. + """The head mark is the tail's mirror, and each takes the nearest drawn cell's fill. + + That means scanning back for a tail cut, and forward for a head cut. """ full = _chips().text() body = full.plain.index("AAAA") # inside the first chip, either way you scan @@ -250,8 +265,10 @@ def test_cut_mark_mirrors_itself_and_reads_the_visible_side() -> None: def test_cut_mark_falls_back_to_the_ellipsis_off_a_chip() -> None: - """An arrow line asks the same question and gets the muted ``…`` — the fallback is - the whole mode test, so no caller has to know which mode drew the line. + """Off a chip, the same question answers with the muted ``…``. + + The fallback is the whole mode test, so no caller has to know which mode drew the + line. """ arrows = PathLine(_chips().hops, mode="plain").text() for side in (ELIDE_HEAD, ELIDE_TAIL): @@ -284,16 +301,20 @@ def test_the_elision_sits_between_chips_rather_than_being_one() -> None: def test_elision_hop_is_the_one_definition_both_surfaces_share() -> None: - """The widget and the mesh walk's trail elide with the same mark *and* the same - gap semantics, so a head the widget hid reads as a tail the scroll hid. + """One elision definition serves both surfaces. + + The widget and the mesh walk's trail elide with the same mark *and* the same gap + semantics, so a head the widget hid reads as a tail the scroll hid. """ mark = elision_hop() assert (mark.label, mark.gap, mark.dim) == ("⋯", True, True) def test_wrapped_cracks_an_over_wide_lone_chip() -> None: - """The one place a chip line is cut mid-hop rather than folded: a hop wider than the - content column stands alone, cracked, still inside the budget. + """A chip wider than the content column is cracked rather than folded. + + It is the one place a chip line is cut mid-hop: the hop stands alone, cracked, and + still inside the budget. """ lines = PathLine([PathHop("N" * 40, key="11aa")], mode="powerline").wrapped(20) assert len(lines) == 1 @@ -302,8 +323,10 @@ def test_wrapped_cracks_an_over_wide_lone_chip() -> None: def test_wrapped_breaks_at_hops_under_a_hanging_indent() -> None: - """Plain lines that continue end with the ``→`` cue; continuations hang at the - indent, stepped in by :data:`WRAP_OFFSET`; every line respects the full width. + """A wrapped plain path breaks at hops, under a hanging indent. + + Lines that continue end with the ``→`` cue; continuations hang at the indent, + stepped in by :data:`WRAP_OFFSET`; every line respects the full width. """ hops = [PathHop(label) for label in ("AAAA", "BBBB", "CCCC", "DDDD")] lines = PathLine(hops, mode="plain").wrapped(16, indent=2) @@ -396,8 +419,10 @@ def test_wrapped_lines_always_fit_their_width() -> None: def test_wrapped_continuation_cue_follows_the_separator() -> None: - """A comma-joined line (the trace screen's wire spec) continues on a comma, not an - arrow — the cue is the separator's own mark, so the spec stays verbatim. + """The continuation cue is the separator's own mark, not always an arrow. + + A comma-joined line (the trace screen's wire spec) continues on a comma, so the + spec stays verbatim. """ hops = [PathHop(label) for label in ("3d63ab99", "7f21cd01", "27aa1122")] lines = PathLine(hops, mode="plain", separator=",").wrapped(20, indent=2) @@ -407,9 +432,10 @@ def test_wrapped_continuation_cue_follows_the_separator() -> None: 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. + """``path_line`` renders the trace flavour exactly as ``path_text`` did. + + The migration bridge: device endpoints with annotated hashes, a named hop, a + prefix-lit unnamed hop, and a dimmed tail, matching character and style alike. """ names = {"aa11bb": "Alice", "3d63ab": "Hub"} hops = [None, "aa11bb", "77ccddee", "3d63ab", None] @@ -423,8 +449,10 @@ 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. + """The message-paths flavour matches ``path_text`` too. + + An unnamed hop stands as its own muted identity hash, annotated with its addressed + byte — the same parity guarantee. """ names = {"3d63abcdef00": "Hub"} hops = ["3d63abcdef00", "e839f2aabb11"] @@ -436,8 +464,10 @@ def test_path_line_factory_matches_path_text_hash_as_name_flavour() -> None: def test_path_line_factory_splices_the_cursor_slot_at_a_hop_index() -> None: - """``cursor`` names the position a chosen hop would take, counted over the rendered - hops and applied after them — so the slot never shifts what its neighbours show. + """``cursor`` splices the insertion slot in at a hop index. + + It names the position a chosen hop would take, counted over the rendered hops and + applied after them, so the slot never shifts what its neighbours show. """ hops = [None, "aa11bb", "3d63ab", None] @@ -452,8 +482,10 @@ def test_path_line_factory_splices_the_cursor_slot_at_a_hop_index() -> None: def test_wrapped_carries_the_cursor_like_any_other_hop() -> None: - """A folded route can't strand its insertion point: the slot is a hop, so it lands - on a line of its own accord — wherever it stands, and whatever the mode. + """A folded route can't strand its insertion point. + + The slot is a hop, so it lands on a line of its own accord — wherever it stands, + and whatever the mode. """ labels = ("AAAA", "BBBB", "CCCC", "DDDD") for at in range(len(labels) + 1): @@ -467,10 +499,12 @@ def test_wrapped_carries_the_cursor_like_any_other_hop() -> None: def test_wrapped_chip_lines_open_on_the_break_they_continue() -> None: - """Chip wrapping never splits a chip; every line ends on the pointed edge, and - every line *but the first* opens on the break's other half — the point notched out - of its own fill in reverse video, so the page shows through and not a trace of the - line above bleeds down. The point always faces the way the path flows. + """Chip wrapping never splits a chip; each line opens on the break it continues. + + Every line ends on the pointed edge, and every line *but the first* opens on the + break's other half — the point notched out of its own fill in reverse video, so the + page shows through and not a trace of the line above bleeds down. The point always + faces the way the path flows. """ hops = [PathHop(label, key=key) for label, key in (("AAAA", "aa"), ("BBBB", "77"), ("CCCC", "3d"), ("DDDD", "f2"))] @@ -490,7 +524,9 @@ def test_wrapped_chip_lines_open_on_the_break_they_continue() -> None: def test_rounded_caps_finish_a_path_only_where_the_font_has_them(monkeypatch) -> None: # noqa: ANN001 - """A full Nerd Font rounds the path's two *outer* ends into a lozenge; a core-only + """Rounded caps finish a path only where the font actually has them. + + A full Nerd Font rounds the path's two *outer* ends into a lozenge; a core-only terminal squares the opening and points the close, never drawing tofu. Interior breaks stay angled either way — a rounded end would read as the path stopping. """ @@ -515,8 +551,10 @@ def test_rounded_caps_finish_a_path_only_where_the_font_has_them(monkeypatch) -> def test_a_seam_between_two_of_one_colour_falls_back_to_the_page() -> None: - """The interlock can't draw a chevron in its own background, so where two chips land - on the same fill the point drops its background and the page cuts the wedge instead. + """Where two chips land on the same fill, the seam falls back to the bare page. + + The interlock can't draw a chevron in its own background, so the point drops its + background and the page cuts the wedge instead. Not a rare accident, either: a mirrored return leg is a run of identically faded hops by construction, and a stretch of keyless hops shares one grey. Either way it stays a @@ -540,10 +578,11 @@ def test_a_seam_between_two_of_one_colour_falls_back_to_the_page() -> None: def test_bare_self_stands_us_on_a_star_and_fades_both_ends() -> None: - """``bare_self`` replaces our name and hash with the app-wide ★ at both ends, and - both fade: setting out from us and landing back on us are fixtures of the route, - not choices, so they wear the same automatic grey as a mirrored return leg — - and, in chips, the same dark slate rather than the loud you white. + """``bare_self`` stands us on a ★ at both ends, and fades both. + + Setting out from us and landing back on us are fixtures of the route, not choices, + so they wear the same automatic grey as a mirrored return leg — and, in chips, the + same dark slate rather than the loud you white. """ hops = [None, *(f"{i:02x}aa" for i in range(6)), None] line = path_line(hops, prefix_bytes=2, self_name="Me", show_hash=True, diff --git a/tests/test_picocalc_chrome.py b/tests/test_picocalc_chrome.py index 032c985..6b30fe7 100644 --- a/tests/test_picocalc_chrome.py +++ b/tests/test_picocalc_chrome.py @@ -149,8 +149,11 @@ def test_bordered_frame_is_unchanged_on_regular() -> None: def test_picocalc_header_brands_the_app_not_the_device() -> None: - """The header_atoms wiring, per JP's spec: picocalc shows ``MeshTerm vX`` (a soldered - radio's port never changes, so the device segment earns nothing), regular keeps both. + """The PicoCalc header brands the app, not the device. + + Per JP's spec for the ``header_atoms`` wiring: picocalc shows ``MeshTerm vX``, + because a soldered radio's port never changes and so the device segment earns + nothing. Regular keeps both. """ from meshterm.ui.menu import _header_segments diff --git a/tests/test_platforms.py b/tests/test_platforms.py index a237f0c..65cdd47 100644 --- a/tests/test_platforms.py +++ b/tests/test_platforms.py @@ -1,5 +1,7 @@ -"""The platform seam: resolution order, the active-platform singleton, and the two -platform specs' own sanity (PicoCalc is strictly the narrower, plainer flavour). +"""Tests for the platform seam. + +Resolution order, the active-platform singleton, and the two platform specs' own +sanity — PicoCalc being strictly the narrower, plainer flavour. """ from __future__ import annotations diff --git a/tests/test_records_screen.py b/tests/test_records_screen.py index d232029..3bafef6 100644 --- a/tests/test_records_screen.py +++ b/tests/test_records_screen.py @@ -146,8 +146,10 @@ def test_record_graph_marks_a_repeater_relay_with_its_triangle() -> None: def test_record_graph_collapses_a_revisited_route_to_distinct_nodes() -> None: - """A boomerang that doubles back seats each node once — the depth graph can't stack a - node on itself — so the revisiting route draws the same graph as its distinct one. + """A revisited route collapses to its distinct nodes in the record graph. + + A boomerang that doubles back seats each node once, the depth graph being unable + to stack a node on itself, so it draws the same graph as its distinct route does. """ looping = _dialog(_record(route=(HUB_ID, FAR_ID, HUB_ID))) distinct = _dialog(_record(route=(HUB_ID, FAR_ID))) diff --git a/tests/test_repeater_admin.py b/tests/test_repeater_admin.py index 26e2b97..841d67c 100644 --- a/tests/test_repeater_admin.py +++ b/tests/test_repeater_admin.py @@ -1,5 +1,7 @@ -"""Repeater-admin tests: the settings catalog, the per-node store, the simulated -remote CLI, and the command-line screen's readline behavior. +"""Tests for repeater admin. + +The settings catalog, the per-node store, the simulated remote CLI, and the +command-line screen's readline behavior. """ from __future__ import annotations diff --git a/tests/test_surface.py b/tests/test_surface.py index bd08c4f..df1d268 100644 --- a/tests/test_surface.py +++ b/tests/test_surface.py @@ -130,8 +130,10 @@ async def test_message_dialog_floats_over_a_blank_backdrop_end_to_end() -> None: async def test_confirm_floats_over_an_existing_popup_end_to_end() -> None: - """A confirm opened over a floating detail popup renders and resolves — the render - loop runs the multi-layer float pool for real (base + detail + confirm), the case + """A confirm floats over an existing popup, end to end. + + Opened over a floating detail popup, it renders and resolves: the render loop runs + the multi-layer float pool for real (base + detail + confirm), which is the case the old single-float compositor stretched the detail to full-frame. """ from prompt_toolkit.input.defaults import create_pipe_input diff --git a/tests/test_termfont.py b/tests/test_termfont.py index cccbbb8..8cbd073 100644 --- a/tests/test_termfont.py +++ b/tests/test_termfont.py @@ -53,8 +53,10 @@ def test_match_recommended_knows_the_nerd_font_spellings() -> None: def test_match_recommended_orders_full_patches_before_core_families() -> None: - """``JetBrainsMono Nerd Font`` must hit its full entry, never the plain - ``JetBrains Mono`` core prefix — first match wins, so full entries lead. + """A full patched family is matched ahead of the core family it is built on. + + ``JetBrainsMono Nerd Font`` must hit its full entry, never the plain ``JetBrains + Mono`` core prefix — first match wins, so the full entries lead. """ assert match_recommended("JetBrainsMono Nerd Font Mono").coverage == FULL assert match_recommended("JetBrains Mono").coverage == CORE @@ -141,8 +143,10 @@ def test_windows_terminal_face_reads_legacy_and_defaults_to_cascadia(tmp_path: P def test_vscode_face_precedence_terminal_over_editor_workspace_over_user( tmp_path: Path, ) -> None: - """``terminal.integrated.fontFamily`` beats ``editor.fontFamily``; within a key - the workspace file beats the user file; the value's head family is judged. + """VS Code's font settings are read in precedence order. + + ``terminal.integrated.fontFamily`` beats ``editor.fontFamily``, and within a key + the workspace file beats the user file. The value's head family is the one judged. """ workspace = tmp_path / "repo" (workspace / ".vscode").mkdir(parents=True) @@ -168,8 +172,10 @@ def test_vscode_face_precedence_terminal_over_editor_workspace_over_user( def test_detect_terminal_font_ladder(tmp_path: Path) -> None: - """WT markers first, then VS Code, then a genuine conhost; TERM disqualifies - the conhost probe (some other emulator is hosting the console). + """The terminal is identified by working down a ladder of markers. + + Windows Terminal first, then VS Code, then a genuine conhost — and ``TERM`` being + set disqualifies the conhost probe, since some other emulator is hosting it. """ wt = detect_terminal_font(_wt_env(tmp_path, {"profiles": {"list": []}})) assert wt is not None and wt.source == "windows-terminal" @@ -204,8 +210,10 @@ def test_powerline_support_matched_font_sets_the_level(tmp_path: Path) -> None: def test_powerline_support_renderer_fallback_and_honest_none(tmp_path: Path) -> None: - """An unmatched face on WT still earns core (the bundled-symbol fallback); the - same face on a bare conhost is an honest ``none``. + """An unmatched face still earns core where the renderer can supply the glyphs. + + On Windows Terminal that is the bundled-symbol fallback; the same face on a bare + conhost is an honest ``none``. """ env = _wt_env(tmp_path, {"profiles": {"list": [{"guid": "{abc-123}"}]}}) wt = _powerline_support(env) # face resolves to Cascadia Mono → no match @@ -215,8 +223,10 @@ def test_powerline_support_renderer_fallback_and_honest_none(tmp_path: Path) -> def test_powerline_support_kitty_ssh_and_unknown() -> None: - """Glyph-drawing terminals earn core by marker; ssh and strangers stay unknown - (the font lives on glass we cannot see). + """A glyph-drawing terminal earns core by its marker; anything else stays unknown. + + ssh sessions and terminals we do not recognise are unknown rather than none: the + font lives on glass we cannot see. """ kitty = _powerline_support({"TERM": "xterm-kitty"}) assert (kitty.level, kitty.source) == (CORE, "renderer:kitty") @@ -227,8 +237,10 @@ def test_powerline_support_kitty_ssh_and_unknown() -> None: def test_installed_recommended_never_raises() -> None: - """The machine scan is best-effort context for a future nudge screen — whatever - the platform answers, it is a RecommendedFont or None, never an exception. + """Scanning the machine for installed fonts never raises. + + It is best-effort context for a future nudge screen, so whatever the platform + answers, the result is a RecommendedFont or None. """ result = installed_recommended() assert result is None or isinstance(result, RecommendedFont) diff --git a/tests/test_theme16.py b/tests/test_theme16.py index 4cb0326..c675373 100644 --- a/tests/test_theme16.py +++ b/tests/test_theme16.py @@ -122,9 +122,11 @@ def test_theme16_backgrounds_stay_in_the_dim_bank() -> None: def test_deploy_script_carries_the_archived_custom_palette() -> None: - """The script's **opt-in** block matches ``theme.vtrgb_lines()`` byte for byte, and - its no-kbd OSC fallback spells the same sixteen RGB values — so the archived custom - palette stays one environment variable away, in sync with `_VT_SLOTS_CUSTOM`. + """The deploy script still carries the archived custom palette, in sync. + + The script's **opt-in** block matches ``theme.vtrgb_lines()`` byte for byte, and + its no-kbd OSC fallback spells the same sixteen RGB values, so the archived + palette stays one environment variable away and in step with `_VT_SLOTS_CUSTOM`. """ script = _FONT_SCRIPT.read_text(encoding="utf-8") assert "MESHTERM_CUSTOM_PALETTE" in script, "the opt-in gate vanished" @@ -334,8 +336,10 @@ def test_canvas_drops_emphasis_where_bold_means_brightness() -> None: def test_font_script_marks_and_fontset_move_together() -> None: - """Every codepoint the script draws or aliases is in the frozen inventory, and every - donor it consumes is out — the two files must change in the same commit. + """The font script and the fontset inventory move together. + + Every codepoint the script draws or aliases is in the frozen inventory, and every + donor it consumes is out, so the two files must change in the same commit. """ script = _FONT_SCRIPT.read_text(encoding="utf-8") marks = {int(m, 16) for m in re.findall(r"^ (0x[0-9A-Fa-f]{4}): art\(", script, re.M)} @@ -364,8 +368,9 @@ def test_font_script_marks_and_fontset_move_together() -> None: def test_specimen_renders_clean_on_picocalc() -> None: - """`meshterm specimen` under picocalc obeys every contract it demonstrates: - ≤53 cells per line, 16-slot SGR only, console-font characters only. + """`meshterm specimen` obeys on picocalc every contract it demonstrates. + + At most 53 cells per line, 16-slot SGR only, and console-font characters only. """ from io import StringIO diff --git a/tests/test_timemachine.py b/tests/test_timemachine.py index b7b63be..bce4655 100644 --- a/tests/test_timemachine.py +++ b/tests/test_timemachine.py @@ -491,9 +491,11 @@ def test_mesh_page_renders_days_rhythm_arrivals_and_ledger(tmp_path: Path) -> No def test_mesh_page_arrivals_key_lane_flexes_with_width(tmp_path: Path) -> None: - """The arrivals key lane fills the terminal: a resolvable full key shows more - than the stored 12 hex, truncating on a byte boundary with an ellipsis; a - narrow terminal floors the lane at its old fixed width. + """The arrivals key lane flexes with the terminal width. + + A resolvable full key shows more than the stored 12 hex, truncating on a byte + boundary with an ellipsis; a narrow terminal floors the lane at its old fixed + width. """ from meshterm.ui.timemachine_screen import _PICK_HASH_W @@ -606,8 +608,10 @@ def test_mesh_page_rhythm_left_edge_aligns_with_the_day_charts(tmp_path: Path) - def test_picker_row_lanes_align_under_the_header() -> None: - """Picker rows lane up under the header; the heard age glows hot, the hash prefix - lights in the key's own hue, and a nameless node's placeholder stays muted. + """Picker row lanes align under the header. + + The heard age glows hot, the hash prefix lights in the key's own hue, and a + nameless node's placeholder stays muted. """ from meshterm.core.models import HeardNode from meshterm.ui.contactlist import ContactRow, _header, _lane @@ -913,7 +917,9 @@ def test_picker_resort_keeps_the_highlight_on_its_node_and_the_filter() -> None: def test_picker_name_lane_is_content_sized_and_hash_lane_flexes() -> None: - """Columns anchor left: the name lane hugs its content and stays put as the window + """The name lane is content-sized, and the hash lane takes what is left. + + Columns anchor left: the name lane hugs its content and stays put as the window widens, and the freed width flows to the hash lane so more of each key shows. """ from meshterm.ui.contactlist import _GAP, _LEAD @@ -985,8 +991,10 @@ def test_picocalc_window_ring_stops_at_30_days() -> None: def _activity_repo(tmp_path: Path) -> Repository: - """A repository seeded with our own outbound life: traces (homed, timed-out, one path - walk), channel + direct messages (some acked, one inbound), and a tx-power sample. + """A repository seeded with our own outbound life. + + Traces (homed, timed-out, one path walk), channel and direct messages (some + acked, one inbound), and a tx-power sample. """ from meshterm.core.models import ( PATH_TRACE_TARGET, @@ -1134,8 +1142,10 @@ def test_picker_lists_you_first_in_the_node_block() -> None: def test_picker_hash_lane_shows_a_heard_nodes_full_key_when_a_contact_holds_it() -> None: - """A heard node stored as a 12-hex prefix shows its whole key when a contact carries it, - so a wide screen reveals more than the twelve stored digits (its stored id still selects). + """A heard node shows its full key when a contact holds it. + + A node stored as a 12-hex prefix reveals more than those twelve digits on a wide + screen, and its stored id still selects it. """ from meshterm.core.models import HeardNode diff --git a/tests/test_topology.py b/tests/test_topology.py index 589bbba..7978c62 100644 --- a/tests/test_topology.py +++ b/tests/test_topology.py @@ -568,8 +568,10 @@ def test_composer_suggestions_follow_the_cursor_anchor() -> None: def test_composer_preview_stands_the_cursor_in_the_route() -> None: - """The insertion point is a hop of the route — the ``+`` slot the next node takes — - and ←/→ slide it along, one position at a time. + """The composer preview stands the cursor in the route itself. + + The insertion point is a hop — the ``+`` slot the next node takes — and ←/→ slide + it along, one position at a time. """ screen = _composer(_topo(), hops=["3d63c6429436"], target=False) @@ -980,8 +982,10 @@ def test_a_stub_whose_only_link_was_its_owner_takes_the_owner_out_with_it() -> N def test_composer_row_cursor_clamps_at_both_ends() -> None: - """The windowed suggestion list does not wrap: ↑ on the first row and ↓ past the last - stay where they are, rather than hauling the window end to end. + """The windowed suggestion list does not wrap. + + ↑ on the first row and ↓ past the last stay where they are, rather than hauling + the window end to end. """ walks = [_traced(("3d", 12.0), ("f2", -5.0), ("3d", -5.5), (None, 12.0))] screen = _composer(_topo(trace_paths=walks)) diff --git a/tests/test_tui.py b/tests/test_tui.py index 68570ad..250890b 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -192,8 +192,10 @@ def test_select_callable_title_re_renders_live() -> None: def test_select_width_aware_title_fits_itself_to_the_row() -> None: - """A title taking the render width fits itself (a route middle-elides); the natural - form still feeds filtering and the dialog's own width measure. + """A width-aware title fits itself to the row it is drawn on. + + A route middle-elides, while the natural form still feeds filtering and the + dialog's own width measure. """ seen: list[int] = [] @@ -211,10 +213,11 @@ def test_select_width_aware_title_fits_itself_to_the_row() -> None: def test_select_row_cracks_a_chip_path_and_ellipsizes_everything_else() -> None: - """A row too wide for the list is *cut*, not truncated: a row carrying a path line - (a trophy walk, a probe candidate) breaks its chip off on the crack, while an - ordinary prose row keeps the ellipsis. The row itself decides which — the list has - no idea it is ever holding a route. + """A row too wide for the list is cut, not truncated. + + A row carrying a path line (a trophy walk, a probe candidate) breaks its chip off + on the crack, while an ordinary prose row keeps the ellipsis. The row itself + decides which — the list has no idea it is ever holding a route. """ route = PathLine( [PathHop(f"NODE{i:02d}", key=f"{i:02x}aa") for i in range(8)], mode="powerline" @@ -228,8 +231,10 @@ def test_select_row_cracks_a_chip_path_and_ellipsizes_everything_else() -> None: def test_select_hscroll_highlight_keeps_the_natural_row() -> None: - """In an hscroll list the highlighted row skips self-fitting — ←→ slide the full - line — while every other row still elides itself to the width. + """In an hscroll list the highlighted row keeps its natural, unfitted text. + + ←→ slide the full line there, while every other row still elides itself to the + width. """ def fitted(width: int) -> str: @@ -676,7 +681,9 @@ def test_reorder_ignores_typed_characters_including_space() -> None: def test_reorder_dialog_width_is_stable_across_states() -> None: - """The natural width fits the widest of rows, hints, and dirty actions — and never + """The reorder dialog is one width in every state it can be in. + + The natural width fits the widest of rows, hints, and dirty actions, and never changes as the user grabs a row or dirties the order, so the popup doesn't resize. """ screen = ReorderScreen("order", ["🔒 alpha", "# b"]) @@ -869,8 +876,10 @@ def test_logo_takes_the_widest_mark_that_fits_the_columns() -> None: def test_compose_startup_shows_footnote_under_logo() -> None: - """A footnote (e.g. a copyright) is drawn muted, immediately under the logo and - right-aligned to the logo's right edge, so the two read as one signed block. + """The startup footnote sits directly under the logo. + + A footnote (a copyright, say) is drawn muted and right-aligned to the logo's own + right edge, so the two read as one signed block. """ screen = SelectScreen("pick", [Choice("a", 1)]) screen.chrome = False @@ -911,8 +920,10 @@ _ACCENT = (129, 140, 248) # the theme's accent border, #818cf8 def _find(plain: str, glyphs: frozenset[str]) -> int: - """Index of the first box glyph from ``glyphs`` (the render console may substitute - rounded corners with square ones on legacy Windows, so tests match the whole family). + """The index of the first box glyph from ``glyphs``. + + The render console may substitute square corners for rounded ones on legacy + Windows, so a test matches the whole family rather than one character. """ return next(i for i, ch in enumerate(plain) if ch in glyphs) @@ -1355,8 +1366,10 @@ def test_device_picker_lists_configured_tcp_profile(tmp_path) -> None: def test_device_picker_lists_configured_serial_profile(tmp_path) -> None: - """A ``[profiles.*]`` serial entry (a soldered ``/dev/ttyS1``) shows up in the picker under - its alias, ready to select — even though pyserial's scan never produces that platform port. + """A configured serial profile is listed in the picker under its alias. + + A soldered ``/dev/ttyS1`` is ready to select even though pyserial's scan never + produces that platform port. """ from meshterm.core.config import DeviceProfile from meshterm.core.device_store import DeviceStore @@ -1384,8 +1397,10 @@ def test_device_picker_lists_configured_serial_profile(tmp_path) -> None: def test_device_picker_serial_profile_yields_to_scanned_port(tmp_path) -> None: - """A serial profile whose port pyserial DOES enumerate is not double-listed — the scanned - row (with real USB metadata) wins over the bare profile. + """A serial profile yields to the scanned port when both name the same thing. + + The row pyserial found wins, because it carries real USB metadata the bare profile + does not. """ from meshterm.core.config import DeviceProfile from meshterm.core.device_store import DeviceStore @@ -2053,11 +2068,12 @@ def test_session_background_is_the_topmost_full_frame_screen() -> None: def test_dispatch_promotes_nav_actions_while_right_ctrl_is_held(monkeypatch) -> None: - """A bare navigation key arriving while the physical right Ctrl is down becomes its - Ctrl chord — the rescue for layouts (Canadian Multilingual Standard) that claim right - Ctrl as a character modifier and strip the ctrl flag from the console's arrow event. - Actions with no Ctrl sibling pass through untouched, as does everything once the key - is released. + """A bare navigation key becomes its Ctrl chord while right Ctrl is held. + + This is the rescue for layouts (Canadian Multilingual Standard) that claim right + Ctrl as a character modifier and strip the ctrl flag from the console's arrow + event. Actions with no Ctrl sibling pass through untouched, as does everything + once the key is released. """ from meshterm.ui.tui import session as session_mod @@ -2082,11 +2098,12 @@ def test_dispatch_promotes_nav_actions_while_right_ctrl_is_held(monkeypatch) -> def test_dispatch_promotes_letter_chords_while_right_ctrl_is_held(monkeypatch) -> None: - """A bare letter typed while the physical right Ctrl is down becomes its Ctrl-letter - chord — the same rescue as the nav keys, for the ^R/^P shortcuts a layout-claimed right - Ctrl would otherwise strip to plain text. Only the mapped letters promote (case-folded, - with the now-stale data dropped); other text — and everything once the key is released — - stays text. + """A bare letter becomes its Ctrl-letter chord while right Ctrl is held. + + The same rescue as the nav keys, for the ^R/^P shortcuts a layout-claimed right + Ctrl would otherwise strip to plain text. Only the mapped letters promote + (case-folded, with the now-stale data dropped); other text — and everything once + the key is released — stays text. """ from meshterm.ui.tui import session as session_mod @@ -2109,10 +2126,12 @@ def test_dispatch_promotes_letter_chords_while_right_ctrl_is_held(monkeypatch) - def test_right_ctrl_rescue_covers_the_sessions_own_chords(monkeypatch) -> None: - """The rescue is app-wide, not screen-actions-only: ^V and ^C are answered by the session - itself, and a layout-claimed right Ctrl must reach them too — right Ctrl-V pastes the - clipboard into a compose line rather than typing a ``v``, right Ctrl-C quits. Neither - pseudo-action is ever forwarded to a screen. + """The right-Ctrl rescue reaches the session's own chords, not just screen actions. + + ^V and ^C are answered by the session itself, and a layout-claimed right Ctrl must + reach them too — right Ctrl-V pastes the clipboard into a compose line rather than + typing a ``v``, right Ctrl-C quits. Neither pseudo-action is ever forwarded to a + screen. """ from meshterm.ui.tui import session as session_mod @@ -2150,9 +2169,11 @@ def test_right_ctrl_rescue_covers_the_sessions_own_chords(monkeypatch) -> None: def test_every_ctrl_letter_chord_is_bound_on_both_ctrl_keys() -> None: - """The chord table drives the prompt_toolkit bindings, so a chord can never be bound for - the left Ctrl without its right-Ctrl rescue (the drift the two used to be able to develop - when the letter map was maintained by hand). + """Every Ctrl-letter chord is bound on both Ctrl keys. + + The chord table drives the prompt_toolkit bindings, so a chord can never be bound + for the left Ctrl without its right-Ctrl rescue — the drift the two used to be + able to develop when the letter map was maintained by hand. """ from prompt_toolkit.keys import Keys @@ -2167,9 +2188,11 @@ def test_every_ctrl_letter_chord_is_bound_on_both_ctrl_keys() -> None: def test_wide_glyph_detection_flags_emoji_not_marks() -> None: - """The desync only ever comes from a width-2 glyph the terminal may draw narrower — an - emoji. Node-type marks, status marks and chart braille are width-1 everywhere, so they - must not trip the check (that they seemed to was the earlier misdiagnosis). + """The wide-glyph check flags emoji, and leaves the app's own marks alone. + + The desync only ever comes from a width-2 glyph the terminal may draw narrower. + Node-type marks, status marks and chart braille are width-1 everywhere, so they + must not trip the check — that they seemed to was the earlier misdiagnosis. """ from meshterm.ui.tui.session import _has_wide_glyph @@ -2210,12 +2233,14 @@ def _row_text(screen, row: int) -> str: def test_a_wide_glyph_frame_upgrades_to_a_full_repaint() -> None: - """prompt_toolkit paints differentially with a *relative* cursor — sound only while every - glyph is one cell. A width-2 glyph the terminal draws in one cell (an emoji in a chat line) - leaves the row's cursor model off; a later paint that skips the unchanged emoji then strands - stale cells to its right. With no remembered frame to compare against, a composed frame - carrying such a glyph drops pt's cached frame, upgrading the next paint to a full - erase_down + redraw. A frame of only width-1 glyphs keeps the fast differential paint — + """A frame carrying a wide glyph upgrades the next paint to a full repaint. + + prompt_toolkit paints differentially with a *relative* cursor — sound only while + every glyph is one cell. A width-2 glyph the terminal draws in one cell (an emoji + in a chat line) leaves the row's cursor model off; a later paint that skips the + unchanged emoji then strands stale cells to its right. With no remembered frame to + compare against, such a frame drops pt's cached frame and takes the erase_down + + redraw instead. A frame of only width-1 glyphs keeps the fast differential paint — this holds wherever the glyph is, floating dialog or not. """ session, _remembered = _repaint_harness() diff --git a/tests/test_walk.py b/tests/test_walk.py index a12b2e2..6fa38ff 100644 --- a/tests/test_walk.py +++ b/tests/test_walk.py @@ -268,8 +268,10 @@ 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. + """Walking back to an earlier node drops the loop. + + 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=[Hub, ALICE]) hub = topo.canonical(Hub.public_key) @@ -980,8 +982,10 @@ def test_walk_collapsed_stand_in_wears_the_selection_white() -> None: def test_walk_link_cursor_clamps_at_both_ends() -> None: - """The windowed link list does not wrap: ↑ on the first row and ↓ past the last stay - where they are, rather than hauling the window end to end. + """The windowed link list does not wrap. + + ↑ on the first row and ↓ past the last stay where they are, rather than hauling + the window end to end. """ screen = WalkScreen( session=_FakeSession(), topo=_hub_topo(14), contacts={}, self_label="us" diff --git a/tests/test_watchtower.py b/tests/test_watchtower.py index 28c6f06..1cd61d3 100644 --- a/tests/test_watchtower.py +++ b/tests/test_watchtower.py @@ -203,8 +203,10 @@ def test_store_round_trips_node_type(tmp_path: Path) -> None: def test_watched_row_type_glyph_and_hued_name(tmp_path: Path) -> None: - """The watchlist row leads with the shared type glyph (own colour) and hues the - name by the entry's key; silence still reads from the trailing tail. + """A watched row leads with its type glyph and hues the name by key. + + The glyph keeps its own colour and the name takes the entry's key-derived hue; + silence still reads from the trailing tail. """ from meshterm.core.watch_store import WatchedNode from meshterm.ui.theme import name_style diff --git a/tests/test_widgets.py b/tests/test_widgets.py index e9520d6..96041f3 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -75,8 +75,11 @@ def test_path_text_empty_reads_as_direct() -> None: def test_path_text_greys_an_unnamed_hop_whole() -> None: - """An unresolvable hop is an unknown node: its hash reads ``node.unknown`` whole, - the prefix never lit — colour marks an identified node, as in the path graph. + """``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