`ruff format .` across the repo: 153 files, no behaviour change. This is the second gate CONTRIBUTING.md promises, and until now it reported 154 files needing reformat — so a first contributor ran the command the guide told them to run and could not tell their own diff from the background. Landed on its own, with nothing else in it, because a whole-repo reformat is only safe to skip past in blame if it is provably nothing but layout. It was checked that way rather than assumed: every changed file was formatted in memory and compared by full `ast.dump()`, and 152 of 153 come out structurally identical (the one exception was a docstring opening on a quote character, fixed in the commit before this one). The string and comment token streams were diffed separately, so a quote flip or an edited comment could not hide inside a re-layout — five quote-style normalisations and sixteen implicit-concatenation joins, all value-identical, and one commented-out line gaining the space after its `#`. `meshterm specimen` is byte-identical on both platforms, and the suite is 1888 green, gallery gate and `test_theme16` included. What actually changed, in order of how much of the diff it is: wrapped calls and signatures rejoin onto one line where they fit inside the hundred columns; the magic trailing comma expands what it is there to expand; complex slices gain their spaces (`[len(noise) :]`); a few redundant parens go. The tables that are laid out to be read were fenced in the previous commit and are untouched here. The aligned trailing-comment column is gone from about ninety lanes that were not fenced — including the `heat.*` ladder in `MESH_THEME`, which cannot be fenced where it sits. Every comment stays on the entry it describes, so nothing there reads wrong; it just no longer lines up. Add this commit to `.git-blame-ignore-revs`, which follows immediately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
450 lines
20 KiB
Python
450 lines
20 KiB
Python
"""P4 chrome contracts: borderless frame, slim header, the F-key lane, dialog gate."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from rich.cells import cell_len
|
|
from rich.text import Text
|
|
|
|
from meshterm.platforms import PICOCALC, REGULAR, set_platform
|
|
from meshterm.ui.tui import frame
|
|
from meshterm.ui.tui.fkeys import (
|
|
DEFAULT_LANE,
|
|
FPair,
|
|
action_for,
|
|
default_lane,
|
|
lane_text,
|
|
strip_lane_atoms,
|
|
)
|
|
from meshterm.ui.tui.screen import ScrollScreen
|
|
from tests.conftest import plain as _plain
|
|
|
|
|
|
def _screen(lines: int = 40) -> ScrollScreen:
|
|
body = Text("\n".join(f"row {i}" for i in range(lines)))
|
|
screen = ScrollScreen(body, title="Chrome probe", floating=False)
|
|
return screen
|
|
|
|
|
|
def _slot_style(row: Text, index: int) -> str:
|
|
"""The style the lane painted onto slot ``index``'s chip (slots are 9 cells + 2 gap)."""
|
|
start = index * 11
|
|
return next(str(span.style) for span in row.spans if span.start <= start < span.end)
|
|
|
|
|
|
def test_borderless_frame_swaps_the_panel_for_a_title_bar() -> None:
|
|
"""On the PicoCalc there is no Panel; a one-row title bar stands in for the whole border.
|
|
|
|
It carries the rule, the title, and the clip arrow. No border column is spent, so the
|
|
body starts at column 0 and owns all 53 cells.
|
|
"""
|
|
set_platform(PICOCALC)
|
|
composed = frame.compose_base(Text("hdr"), _screen(), "hint", 53, 26).split("\n")
|
|
assert len(composed) == 26
|
|
plain = [_plain(line) for line in composed]
|
|
# Row 1 (under the one-line header) is the title bar: rule + title + clip arrow.
|
|
assert "Chrome probe" in plain[1] and "─" in plain[1]
|
|
assert "↓" in plain[1] # 40 rows into a 23-row viewport: more below
|
|
# No Panel borders anywhere — the body owns the full width.
|
|
assert not any("│" in line or "╭" in line for line in plain)
|
|
assert all(cell_len(line) <= 53 for line in plain)
|
|
# The body's first row starts at column 0 of row 2 (no panel padding).
|
|
assert plain[2].startswith("row 0")
|
|
|
|
|
|
def _bar_text(
|
|
title: str, hint: str, cols: int = 53, *, above: bool = False, below: bool = True
|
|
) -> Text:
|
|
"""The borderless title bar for a screen with ``title`` and ``hint``, as styled Text."""
|
|
screen = ScrollScreen(Text("x"), title=title, floating=False)
|
|
screen._footer_hint = hint
|
|
return frame._title_bar(screen, cols, above, below)
|
|
|
|
|
|
def _bar(title: str, hint: str, cols: int = 53, *, below: bool = True) -> str:
|
|
"""The borderless title bar's plain text for a screen with ``title`` and ``hint``."""
|
|
return _bar_text(title, hint, cols, below=below).plain
|
|
|
|
|
|
def _arrow_styles(bar: Text) -> tuple[str, str]:
|
|
"""The styles the bar painted on its two clip arrows, ``(up, down)``."""
|
|
return tuple(
|
|
next(
|
|
str(span.style)
|
|
for span in bar.spans
|
|
if span.start <= index < span.end and span.end - span.start == 1
|
|
)
|
|
for index in (0, 1)
|
|
)
|
|
|
|
|
|
def test_the_title_bar_says_how_to_leave_the_screen() -> None:
|
|
"""This platform has no footer hint line, so the way out rides the bar's tail.
|
|
|
|
The F-key lane stands where the footer would be and only advertises what its slots do,
|
|
which left Esc — the key every screen answers to, and the reason no screen carries a
|
|
*Back* row — completely unadvertised (JP, 2026-08-30). It lands last, past the title,
|
|
with the clip arrows holding the row's other end.
|
|
"""
|
|
bar = _bar("Chrome probe", "↑↓ move · Enter open · Esc back")
|
|
|
|
assert bar.startswith("↑↓ ")
|
|
assert bar.rstrip().endswith("Esc back")
|
|
assert "Chrome probe" in bar
|
|
|
|
|
|
def test_the_clip_arrows_are_always_drawn_and_say_it_in_colour() -> None:
|
|
"""Both arrows are fixed furniture at the row's left edge; colour reads the scroll.
|
|
|
|
They used to appear and vanish, and a half-shown pair left a blank cell standing in
|
|
for the missing one — the reader had to compare the row against a memory of itself
|
|
(JP, 2026-08-31). Now the pair never moves and never changes width, and an arrow
|
|
whose direction has more takes the border's own accent while one with nothing that way
|
|
drops to muted: the F-key lane's live/dim language, one row up.
|
|
"""
|
|
hint = "↑↓ move · Esc back"
|
|
assert _arrow_styles(_bar_text("Contacts", hint, above=True, below=True)) == (
|
|
"accent",
|
|
"accent",
|
|
)
|
|
assert _arrow_styles(_bar_text("Contacts", hint, above=False, below=True)) == (
|
|
"muted",
|
|
"accent",
|
|
)
|
|
assert _arrow_styles(_bar_text("Contacts", hint, above=True, below=False)) == (
|
|
"accent",
|
|
"muted",
|
|
)
|
|
# A body that fits whole keeps the pair, both dim — nothing appears or disappears.
|
|
assert _arrow_styles(_bar_text("Contacts", hint, above=False, below=False)) == (
|
|
"muted",
|
|
"muted",
|
|
)
|
|
|
|
widths = {
|
|
_bar_text("Contacts", hint, above=a, below=b).plain
|
|
for a in (False, True)
|
|
for b in (False, True)
|
|
}
|
|
assert len(widths) == 1, "the bar's text must not shift as the body scrolls"
|
|
|
|
|
|
def test_the_bar_speaks_the_screen_s_own_esc_verb() -> None:
|
|
"""Lifted off the screen's own footer hint, so each surface keeps its true verb."""
|
|
assert _bar("MeshTerm", "↑↓ move · Enter open · Esc quit").endswith("Esc quit")
|
|
assert _bar("Path width", "↑↓ move · Enter set · Esc keep").endswith("Esc keep")
|
|
# A hint that names no way out advertises none — the lane's rule, one row up.
|
|
assert "Esc" not in _bar("Working", "↑↓ move")
|
|
|
|
|
|
def test_the_esc_hint_gives_way_before_it_crowds_the_title() -> None:
|
|
"""Compact by construction: the verb goes first, then the atom, and the title stays.
|
|
|
|
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 — 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 — Hilltop-Repeater over a longer spec", "↑↓ move · Esc back")
|
|
assert "Esc" not in crowded
|
|
|
|
for bar in (verbless, crowded):
|
|
assert "Hilltop-Repeater" in bar and cell_len(bar) <= 53
|
|
|
|
|
|
def test_bordered_frame_is_unchanged_on_regular() -> None:
|
|
"""Regular keeps its Panel — the borderless frame is the console's shape, not the app's."""
|
|
set_platform(REGULAR)
|
|
composed = frame.compose_base(Text("hdr"), _screen(), "hint", 72, 24).split("\n")
|
|
plain = [_plain(line) for line in composed]
|
|
assert any("╭" in line or "┌" in line for line in plain), "regular keeps its Panel"
|
|
|
|
|
|
def test_picocalc_header_brands_the_app_not_the_device() -> None:
|
|
"""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
|
|
|
|
class _Badges:
|
|
def unread_total(self) -> int:
|
|
return 0
|
|
|
|
def unacked_count(self) -> int:
|
|
return 0
|
|
|
|
class _Ctx:
|
|
mock = True
|
|
chat = _Badges()
|
|
watchtower = _Badges()
|
|
|
|
set_platform(PICOCALC)
|
|
text = "".join(seg.plain for seg in _header_segments(_Ctx(), {}))
|
|
assert "MeshTerm" in text
|
|
assert "simulator" not in text # the device atom is composed out
|
|
assert "pulse" in PICOCALC.header_atoms # the sparkline takes the remaining room
|
|
set_platform(REGULAR)
|
|
text = "".join(seg.plain for seg in _header_segments(_Ctx(), {}))
|
|
assert "MeshTerm" in text and "simulator" in text
|
|
|
|
|
|
def test_fkey_lane_resolution_and_banks() -> None:
|
|
"""What the shared lane claims, and what it leaves alone.
|
|
|
|
The pager takes F4/F5, each jump rides the Shift half of the pager heading for it,
|
|
F1-F3 stay free for the screen's own verbs, and neither Enter nor Esc takes a slot.
|
|
"""
|
|
lane = DEFAULT_LANE
|
|
# F4/F5 (paging — no physical key at all) carry the pager, and each jump rides the
|
|
# Shift half of the very pager heading for it: Home behind Page ↑, End behind Page ↓.
|
|
assert action_for(lane, 4) == "pagedown" and action_for(lane, 9) == "end"
|
|
assert action_for(lane, 5) == "pageup" and action_for(lane, 10) == "home"
|
|
# F1-F3 are free on every screen — the shared lane claims none of them.
|
|
for number in (1, 2, 3, 6, 7, 8):
|
|
assert action_for(lane, number) is None
|
|
# Enter/Esc never occupy a slot — both keys are already close at hand.
|
|
assert "enter" not in {action_for(lane, n) for n in range(1, 11)}
|
|
assert "escape" not in {action_for(lane, n) for n in range(1, 11)}
|
|
|
|
|
|
def test_fkey_lane_text_fits_and_flips() -> None:
|
|
"""The lane fills exactly 53 cells in both banks, and a long label is clipped, not wrapped.
|
|
|
|
The directional pair rises toward its outer key, and a free slot draws as a bare
|
|
key number rather than an empty gap.
|
|
"""
|
|
primary = lane_text(DEFAULT_LANE)
|
|
shifted = lane_text(DEFAULT_LANE, shifted=True)
|
|
assert cell_len(primary.plain) == 53 and cell_len(shifted.plain) == 53
|
|
# A directional pair rises to the right: down/out left, up/in right — and each
|
|
# companion keeps its slot's end of that axis.
|
|
assert "F4 Page ↓" in primary.plain and "F5 Page ↑" in primary.plain
|
|
assert "F9 Bottom" in shifted.plain and "10 Top" in shifted.plain
|
|
# The free bank renders as bare, unfilled key numbers in both banks.
|
|
for number in (1, 2, 3):
|
|
assert f"F{number} " in primary.plain
|
|
assert f"F{number + 5} " in shifted.plain
|
|
wide = [FPair("Muchtoolonglabel", "a", "Muchtoolonglabel", "b")] * 5
|
|
assert cell_len(lane_text(wide).plain) == 53 # clipped to the slot budget
|
|
assert cell_len(lane_text(wide, shifted=True).plain) == 53
|
|
|
|
|
|
def test_fkey_slot_dims_when_its_action_is_unavailable() -> None:
|
|
"""An unavailable slot keeps its label and drops its fill — it never offers a dead key."""
|
|
lane = [FPair("Paths", "paths", "Retry", "retry", enabled=False)] + [None] * 4
|
|
primary = lane_text(lane)
|
|
shifted = lane_text(lane, shifted=True)
|
|
|
|
# The label survives: the lane still says what F1 is for, just not that it acts now.
|
|
assert "F1 Paths" in primary.plain and _slot_style(primary, 0) == "muted"
|
|
# Its Shift companion is untouched — the two banks gate independently.
|
|
assert "F6 Retry" in shifted.plain and _slot_style(shifted, 0) == "fkey.chip.shift"
|
|
# The row is still exactly the lane's width, dim chips and all.
|
|
assert cell_len(primary.plain) == 53 and cell_len(shifted.plain) == 53
|
|
# Dimming is presentational: the key still resolves, and the screen's handler no-ops.
|
|
assert action_for(lane, 1) == "paths"
|
|
|
|
|
|
def test_default_lane_dims_every_nav_slot_when_nothing_moves() -> None:
|
|
"""A screen with nothing to move gets the shared lane back inert, not absent."""
|
|
assert default_lane() is DEFAULT_LANE # the live lane is the constant itself
|
|
dim = default_lane(nav=False)
|
|
|
|
assert [pair.label for pair in dim if pair] == ["Page ↓", "Page ↑"]
|
|
assert not any(pair.enabled or pair.opp_enabled for pair in dim if pair)
|
|
row = lane_text(dim)
|
|
assert cell_len(row.plain) == 53 and "F4 Page ↓" in row.plain and "F5 Page ↑" in row.plain
|
|
assert {str(span.style) for span in row.spans} == {"muted"}
|
|
# Both banks dim together: the jump behind a dead pager is just as dead.
|
|
assert {str(span.style) for span in lane_text(dim, shifted=True).spans} == {"muted"}
|
|
|
|
|
|
def test_lane_is_built_after_the_body_renders() -> None:
|
|
"""The frame defers the lane, so its gates read this paint's metrics, not the last one's."""
|
|
set_platform(PICOCALC)
|
|
screen = _screen() # 40 body rows into a 23-row viewport: it overflows
|
|
seen: dict[str, bool] = {}
|
|
|
|
def build() -> Text:
|
|
seen["nav"] = all(pair.enabled for pair in screen.fkey_lane if pair)
|
|
return lane_text(screen.fkey_lane)
|
|
|
|
composed = frame.compose_base(Text("hdr"), screen, "hint", 53, 26, footer_lane=build)
|
|
|
|
assert seen["nav"] is True # lit on the very first paint, with no stale frame to lag
|
|
assert "F4 Page ↓" in _plain(composed.split("\n")[-1])
|
|
|
|
|
|
def test_scroll_screen_lane_tracks_whether_its_body_overflows() -> None:
|
|
"""The result window's nav slots light only once there is something to scroll to."""
|
|
screen = _screen()
|
|
screen.note_metrics(4, 20) # the whole body fits the viewport
|
|
assert not any(pair.enabled for pair in screen.fkey_lane if pair)
|
|
screen.note_metrics(80, 20) # taller than the viewport
|
|
assert all(pair.enabled for pair in screen.fkey_lane if pair)
|
|
|
|
|
|
def test_select_lane_promotes_the_section_jumps_only_where_there_are_sections() -> None:
|
|
"""Ctrl+PgUp/PgDn is unreachable on a keyboard with no PgUp, so a grouped list lanes it."""
|
|
from meshterm.ui.tui.select import Choice, SelectScreen, Separator
|
|
|
|
flat = SelectScreen("Flat", [Choice("one", 1), Choice("two", 2)])
|
|
# No headings anywhere: sections are not a thing on this list, so the slots are empty
|
|
# rather than dim — and F1/F2 resolve to nothing at all.
|
|
assert flat.fkey_lane[0] is None and flat.fkey_lane[1] is None
|
|
assert action_for(flat.fkey_lane, 1) is None
|
|
|
|
grouped = SelectScreen(
|
|
"Grouped",
|
|
[
|
|
Separator("── Near ──"),
|
|
Choice("alpha", 1),
|
|
Choice("beta", 2),
|
|
Separator("── Far ──"),
|
|
Choice("gamma", 3),
|
|
],
|
|
)
|
|
lane = grouped.fkey_lane
|
|
assert [pair.label for pair in lane[:2]] == ["Sect ↑", "Sect ↓"]
|
|
# A pair rises toward its outer key: on this left-edge pair, up takes F1.
|
|
assert action_for(lane, 1) == "ctrl_pageup" and action_for(lane, 2) == "ctrl_pagedown"
|
|
assert all(pair.enabled for pair in lane[:2])
|
|
|
|
# A filter that collapses the list onto one section keeps the labels and dims them:
|
|
# the sections are still a thing here, they just have nowhere to jump right now.
|
|
for ch in "gam":
|
|
grouped.handle("text", ch)
|
|
dim = grouped.fkey_lane
|
|
assert [pair.label for pair in dim[:2]] == ["Sect ↑", "Sect ↓"]
|
|
assert not any(pair.enabled for pair in dim[:2])
|
|
|
|
|
|
def test_dialogs_draw_no_lane_at_all() -> None:
|
|
"""A prompt has nothing to page, so it shows bare key numbers, not a dim pager."""
|
|
from meshterm.ui.tui.fkeys import EMPTY_LANE
|
|
from meshterm.ui.tui.prompt import ButtonDialog, ConfirmScreen, TextScreen
|
|
|
|
for screen in (
|
|
TextScreen("Name", prompt="Call it what?"),
|
|
ConfirmScreen("Sure?"),
|
|
ButtonDialog("Reboot the node?", ["Cancel", "Reboot"]),
|
|
):
|
|
assert list(screen.fkey_lane) == list(EMPTY_LANE)
|
|
row = lane_text(screen.fkey_lane)
|
|
assert cell_len(row.plain) == 53
|
|
assert {str(span.style) for span in row.spans} == {"muted"}
|
|
|
|
|
|
def test_map_locate_is_reachable_from_both_control_keys() -> None:
|
|
"""``locate`` is bound as a chord app-wide, so both Ctrl keys and F2 reach the same action.
|
|
|
|
The letter is the mnemonic of the action — ^U for *you* — so it is the same chord on
|
|
every screen that can point at our own node, not one screen's initial.
|
|
"""
|
|
from prompt_toolkit.keys import Keys
|
|
|
|
from meshterm.ui.tui.session import _CTRL_LETTER_CHORDS, _KEY_ACTIONS
|
|
|
|
assert _CTRL_LETTER_CHORDS["u"] == "locate" # the right-Ctrl rescue's half
|
|
assert _KEY_ACTIONS[Keys.ControlU] == "locate" # the ordinary binding, generated from it
|
|
|
|
|
|
def test_host_battery_reads_the_sysfs_supply(tmp_path, monkeypatch) -> None:
|
|
"""The PicoCalc battery path: a true percent and charging flag straight from sysfs."""
|
|
from meshterm.services import battery_service
|
|
|
|
(tmp_path / "capacity").write_text("53\n")
|
|
(tmp_path / "status").write_text("Discharging\n")
|
|
(tmp_path / "voltage_now").write_text("4012000\n")
|
|
monkeypatch.setattr(battery_service, "_HOST_SUPPLY", tmp_path)
|
|
service = battery_service.BatteryService(ctx=None)
|
|
service._poll_host()
|
|
reading = service.reading()
|
|
assert reading is not None
|
|
assert (reading.percent, reading.charging, reading.millivolts) == (53, False, 4012)
|
|
|
|
(tmp_path / "status").write_text("Charging\n")
|
|
service._poll_host()
|
|
assert service.reading().charging is True
|
|
|
|
monkeypatch.setattr(battery_service, "_HOST_SUPPLY", tmp_path / "gone")
|
|
service._poll_host()
|
|
assert service.reading() is None # unreadable supply = absent, header draws nothing
|
|
|
|
|
|
def test_dialog_gate_shrinks_on_picocalc() -> None:
|
|
"""A dialog is allowed fewer cells on the PicoCalc, the screen being narrower."""
|
|
from meshterm.ui.surface import _dialog_max_cells
|
|
|
|
set_platform(PICOCALC)
|
|
assert _dialog_max_cells() == 43
|
|
set_platform(REGULAR)
|
|
assert _dialog_max_cells() == 76
|
|
|
|
|
|
# --- the dialog border's hint, against the lane one row below it ------------------------
|
|
|
|
|
|
def _dialog(hint: str) -> ScrollScreen:
|
|
"""A floating screen carrying ``hint``, on the shared pager lane."""
|
|
screen = ScrollScreen(Text("body"), title="Probe")
|
|
screen._footer_hint = hint
|
|
return screen
|
|
|
|
|
|
def test_a_dialog_keeps_its_hint_where_the_lane_is_the_footer() -> None:
|
|
"""There is no hint line here, so a floating box's border is the only place keys read.
|
|
|
|
The desktop drops the border hint entirely (the footer row below repeats it word for
|
|
word); this platform's footer row is the lane, which speaks only for its five chips,
|
|
so Enter, Esc and the arrows have nowhere else to be said.
|
|
"""
|
|
screen = _dialog("↑↓ move · Enter select · Esc back")
|
|
set_platform(REGULAR)
|
|
assert frame._dialog_hint(screen) == ""
|
|
set_platform(PICOCALC)
|
|
assert "Esc back" in _plain(frame.compose_dialog(screen, 53, 26))
|
|
|
|
|
|
def test_the_dialog_hint_drops_what_the_lane_already_says() -> None:
|
|
"""An atom whose every key is a chip on the very next row is the same claim twice."""
|
|
set_platform(PICOCALC)
|
|
screen = _dialog("↑↓ move · PgUp/PgDn scroll · Home/End ends · Enter select · Esc back")
|
|
# The shared lane pages on F4/F5 and jumps to either end behind their Shift halves.
|
|
assert frame._dialog_hint(screen) == "↑↓ move · Enter select · Esc back"
|
|
assert "PgUp" not in _plain(frame.compose_dialog(screen, 53, 26))
|
|
|
|
|
|
def test_the_dialog_hint_is_resolved_on_every_paint() -> None:
|
|
"""Never folded into a screen's hint once: both halves move while the screen is up.
|
|
|
|
The packet viewer rewrites its own hint as the list it pages through grows past one
|
|
entry (JP, 2026-08-31), and every screen reads its lane fresh each frame.
|
|
"""
|
|
set_platform(PICOCALC)
|
|
screen = _dialog("Esc close")
|
|
assert frame._dialog_hint(screen) == "Esc close"
|
|
screen._footer_hint = "↑↓ newer/older · PgUp/PgDn scroll · Home/End ends · Esc close"
|
|
assert frame._dialog_hint(screen) == "↑↓ newer/older · Esc close"
|
|
|
|
|
|
def test_an_atom_the_lane_only_half_covers_stands() -> None:
|
|
"""Half a truth is worse than the whole atom: it survives unless every key is a chip."""
|
|
pager_only = (None, None, None, FPair("Page ↓", "pagedown"), FPair("Page ↑", "pageup"))
|
|
assert strip_lane_atoms("PgUp/PgDn scroll", pager_only) == ""
|
|
# No Shift bank here, so Home/End are nowhere on the lane.
|
|
assert strip_lane_atoms("Home/End ends", pager_only) == "Home/End ends"
|
|
# The atom documents the arrows too, and no slot ever claims those.
|
|
assert strip_lane_atoms("↑↓ PgUp/PgDn scroll", DEFAULT_LANE) == "↑↓ PgUp/PgDn scroll"
|
|
# A second key riding in the verb half keeps the atom whole (the map's region/you).
|
|
assert strip_lane_atoms("Home/^U region/you", DEFAULT_LANE) == "Home/^U region/you"
|
|
|
|
|
|
def test_a_dimmed_chip_still_covers_its_atom() -> None:
|
|
"""Dim says *a thing here, just not right now* — the reader has been told where it is."""
|
|
assert strip_lane_atoms("PgUp/PgDn scroll", default_lane(nav=False)) == ""
|