diff options
| author | user <user@clank> | 2026-08-17 14:36:59 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-17 14:36:59 +0200 |
| commit | c0022776d0912042819e4ea330cf7491a8196b45 (patch) | |
| tree | 3c5d547f273d8304e7cd6834fd5a2c730a495064 /tests/test_tabs.py | |
| parent | sl0ppty: ruff formats the Python, and sorts its imports (diff) | |
| download | slosh-c0022776d0912042819e4ea330cf7491a8196b45.tar.gz slosh-c0022776d0912042819e4ea330cf7491a8196b45.tar.xz slosh-c0022776d0912042819e4ea330cf7491a8196b45.zip | |
sl0ppty: ruff format the Python
The bumpy one, again. Every `.py` outside `vendor/` plus the three `contrib/`
scripts that are Python with no extension: 60 files, 6,714 lines rewritten, no
behaviour changed. Nothing here is worth reading -- the next commit adds it to
`.git-blame-ignore-revs` so `git blame` walks past it.
It is a bigger reformat than the C was, 60% of the lines against 17%, and for a
reason worth writing down rather than tuning away: ruff and black break a call that
does not fit onto one argument per line, where this code aligned the continuation
under the opening paren. The test suite is thousands of `check("some prose", cond,
detail)` calls, so nearly every one of them changed shape and the suite grew 3,308
lines. Line length was the one lever that moved it -- 120 columns would have been a
40% reformat and no growth at all -- and it was left at 88, because picking an
unusual width to dodge a one-time diff is paying forever to avoid paying once.
Import sorting came with it. Two files had `import os, pty, uuid, select, struct,
sys, termios, time, fcntl, signal` on one line and now have ten.
Verified after, not before, because a formatter that alters behaviour would be
worse than none:
- `make retest`, 1801 checks green -- and this reformats the suite that does the
checking, including `harness.py`, which every one of those files imports.
- `make test-live`, the tty tests the fast suite skips. They matter here more than
usual: `live_input.py` and `live_m0.py` are two of the files whose comma-imports
were split, and nothing in the fast suite would have noticed if that broke them.
- All 60 files still compile, `make fmt` twice in a row changes nothing the second
time, and `ruff check --select I` has nothing left to say.
Diffstat (limited to 'tests/test_tabs.py')
| -rw-r--r-- | tests/test_tabs.py | 396 |
1 files changed, 257 insertions, 139 deletions
diff --git a/tests/test_tabs.py b/tests/test_tabs.py index a05cef1..3ae570a 100644 --- a/tests/test_tabs.py +++ b/tests/test_tabs.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """M3: tabs, purposes and the JSON control API.""" + import os import sys import tempfile @@ -23,19 +24,26 @@ def test_tabs(): tabs = s.tabs() check("C-a c makes a tab", len(tabs) == 2, str(tabs)) check("the new tab is active", tabs[1]["active"] and not tabs[0]["active"]) - check("each tab has its own pane", [t["panes"] for t in tabs] == [1, 1], - str(tabs)) + check( + "each tab has its own pane", [t["panes"] for t in tabs] == [1, 1], str(tabs) + ) snap = s.snapshot() check("the new tab's pane is empty", "in-tab-one" not in snap.screen()) - check("the tab strip is drawn", "1" in snap.line(1) and "2" in snap.line(1), - repr(snap.line(1))) + check( + "the tab strip is drawn", + "1" in snap.line(1) and "2" in snap.line(1), + repr(snap.line(1)), + ) s.key(PREV) s.settle() check("C-a shift+tab goes back", s.tabs()[0]["active"]) - check("the first tab kept its content", "in-tab-one" in s.snapshot().screen(), - repr(s.snapshot().screen()[:200])) + check( + "the first tab kept its content", + "in-tab-one" in s.snapshot().screen(), + repr(s.snapshot().screen()[:200]), + ) s.key("2") s.settle() @@ -47,14 +55,18 @@ def test_tabs(): active = [t["index"] for t in s.tabs() if t["active"]] s.key("n") s.settle() - check("C-a n no longer cycles tabs", - [t["index"] for t in s.tabs() if t["active"]] == active, - str(s.tabs())) + check( + "C-a n no longer cycles tabs", + [t["index"] for t in s.tabs() if t["active"]] == active, + str(s.tabs()), + ) s.key("p") s.settle() - check("nor does C-a p, which opens the palette", - [t["index"] for t in s.tabs() if t["active"]] == active, - str(s.tabs())) + check( + "nor does C-a p, which opens the palette", + [t["index"] for t in s.tabs() if t["active"]] == active, + str(s.tabs()), + ) s.send(r"\e") # put the palette away again @@ -71,13 +83,18 @@ def test_background_tabs_keep_running(): s.settle() s.key(NEXT) # away again s.settle() - check("a background pane still accepts input", - any(p["tab"] == 1 for p in s.panes()), str(s.panes())) + check( + "a background pane still accepts input", + any(p["tab"] == 1 for p in s.panes()), + str(s.panes()), + ) s.api("select-tab", index=1) s.settle() - check("its output survived being off-screen", - "still-alive" in s.snapshot().screen(), - repr(s.snapshot().screen()[:200])) + check( + "its output survived being off-screen", + "still-alive" in s.snapshot().screen(), + repr(s.snapshot().screen()[:200]), + ) def test_tab_click(): @@ -94,8 +111,7 @@ def test_tab_click(): check("the tab label is clickable", action == "tab:1", str(action)) s.click(x, y) s.settle() - check("clicking a tab switches to it", s.tabs()[0]["active"], - str(s.tabs())) + check("clicking a tab switches to it", s.tabs()[0]["active"], str(s.tabs())) def test_closing_a_tab(): @@ -103,7 +119,7 @@ def test_closing_a_tab(): s.settle() s.key("c") s.settle() - s.key("\\\\") # two panes in tab 2 + s.key("\\\\") # two panes in tab 2 s.settle() check("tab 2 has two panes", s.tabs()[1]["panes"] == 2, str(s.tabs())) s.key("x") @@ -111,10 +127,11 @@ def test_closing_a_tab(): check("closing one pane keeps the tab", len(s.tabs()) == 2, str(s.tabs())) s.key("x") s.settle() - check("closing the last pane closes the tab", len(s.tabs()) == 1, - str(s.tabs())) - check("the surviving tab is active and usable", - s.tabs()[0]["active"] and s.api("alive")["alive"]) + check("closing the last pane closes the tab", len(s.tabs()) == 1, str(s.tabs())) + check( + "the surviving tab is active and usable", + s.tabs()[0]["active"] and s.api("alive")["alive"], + ) def test_closing_a_whole_tab_is_one_verb(): @@ -136,8 +153,11 @@ def test_closing_a_whole_tab_is_one_verb(): s.settle() check("one press closes the whole tab", len(s.tabs()) == 1, str(s.tabs())) screen = s.snapshot().screen() - check("and says how much went with it", - "closed tab" in screen and "3 panes" in screen, screen) + check( + "and says how much went with it", + "closed tab" in screen and "3 panes" in screen, + screen, + ) check("the session is still running", s.api("alive")["alive"]) @@ -151,8 +171,11 @@ def test_the_last_tab_is_refused_rather_than_obeyed(): s.settle() check("the tab is still there", len(s.tabs()) == 1, str(s.tabs())) check("the session is still running", s.api("alive")["alive"]) - check("and it says what to press instead", "quit" in s.snapshot().screen(), - s.snapshot().screen()) + check( + "and it says what to press instead", + "quit" in s.snapshot().screen(), + s.snapshot().screen(), + ) def test_close_tab_is_reachable_without_the_key(): @@ -160,14 +183,20 @@ def test_close_tab_is_reachable_without_the_key(): s.settle() s.send(r"\x01?") s.settle() - check("the cheatsheet lists it", "close this tab" in s.snapshot().screen(), - s.snapshot().screen()) + check( + "the cheatsheet lists it", + "close this tab" in s.snapshot().screen(), + s.snapshot().screen(), + ) s.send(r"\x01q") # any key dismisses the sheet; this one is swallowed s.settle() s.key("p") s.send("close this tab") - check("so does the palette", "close this tab" in s.snapshot().screen(), - s.snapshot().screen()) + check( + "so does the palette", + "close this tab" in s.snapshot().screen(), + s.snapshot().screen(), + ) def test_purpose_trust_model(): @@ -176,35 +205,61 @@ def test_purpose_trust_model(): s.settle() pane = s.pane() - r = s.api("set-purpose", target="pane", id=pane["id"], - purpose="agent:main", declared=True) + r = s.api( + "set-purpose", + target="pane", + id=pane["id"], + purpose="agent:main", + declared=True, + ) check("a declared purpose is accepted", r["ok"], str(r)) p = s.pane() check("the purpose is reported", p["purpose"] == "agent:main", str(p)) check("and marked declared", p["purpose_declared"], str(p)) - r = s.api("set-purpose", target="pane", id=pane["id"], - purpose="evil:overridden", declared=False) - check("an in-band purpose cannot override a declared one", not r["ok"], - str(r)) - check("the declared purpose is intact", - s.pane()["purpose"] == "agent:main", str(s.pane())) + r = s.api( + "set-purpose", + target="pane", + id=pane["id"], + purpose="evil:overridden", + declared=False, + ) + check("an in-band purpose cannot override a declared one", not r["ok"], str(r)) + check( + "the declared purpose is intact", + s.pane()["purpose"] == "agent:main", + str(s.pane()), + ) - r = s.api("set-purpose", target="tab", id=s.tabs()[0]["id"], - purpose="project:foo bar/../$(rm -rf)", declared=True) - check("a tab purpose is sanitised on ingest", - s.tabs()[0]["purpose"] == "project:foobar/../rm-rf", - str(s.tabs()[0]["purpose"])) + r = s.api( + "set-purpose", + target="tab", + id=s.tabs()[0]["id"], + purpose="project:foo bar/../$(rm -rf)", + declared=True, + ) + check( + "a tab purpose is sanitised on ingest", + s.tabs()[0]["purpose"] == "project:foobar/../rm-rf", + str(s.tabs()[0]["purpose"]), + ) with Session(SH, cols=60, rows=12) as s: s.settle() pane = s.pane() - r = s.api("set-purpose", target="pane", id=pane["id"], - purpose="guessed:by-the-pane", declared=False) - check("an in-band purpose is allowed when nothing is declared", r["ok"], - str(r)) - check("and does not lock the slot", - not s.pane()["purpose_declared"], str(s.pane())) + r = s.api( + "set-purpose", + target="pane", + id=pane["id"], + purpose="guessed:by-the-pane", + declared=False, + ) + check("an in-band purpose is allowed when nothing is declared", r["ok"], str(r)) + check( + "and does not lock the slot", + not s.pane()["purpose_declared"], + str(s.pane()), + ) def test_json_api(): @@ -213,55 +268,68 @@ def test_json_api(): base = s.pane()["id"] r = s.api("split", dir="rows", id=base) - check("split answers with the new pane id", r["ok"] and r["id"] != base, - str(r)) + check("split answers with the new pane id", r["ok"] and r["id"] != base, str(r)) new = r["id"] r = s.api("focus", id=base) check("focus by id works", r["ok"] and r["id"] == base, str(r)) - check("focus by id is reflected in the layout", - [p for p in s.panes() if p["focused"]][0]["id"] == base) + check( + "focus by id is reflected in the layout", + [p for p in s.panes() if p["focused"]][0]["id"] == base, + ) r = s.api("new-tab", name="build", purpose="project:x.deadbeef") check("new-tab answers with an id", r["ok"] and r["id"], str(r)) t = [t for t in s.tabs() if t["id"] == r["id"]][0] - check("new-tab records name and declared purpose", - t["name"] == "build" and t["purpose"] == "project:x.deadbeef" - and t["purpose_declared"], str(t)) + check( + "new-tab records name and declared purpose", + t["name"] == "build" + and t["purpose"] == "project:x.deadbeef" + and t["purpose_declared"], + str(t), + ) r = s.api("snapshot", format="text") - check("snapshot text comes back as JSON", r["ok"] and "text" in r, - str(r)[:80]) + check("snapshot text comes back as JSON", r["ok"] and "text" in r, str(r)[:80]) r = s.api("close", id=new) check("close by id works", r["ok"], str(r)) - check("unknown commands are refused, not fatal", - s.api("nope")["error"] == "unknown cmd") + check( + "unknown commands are refused, not fatal", + s.api("nope")["error"] == "unknown cmd", + ) r = s.api("focus", id=9999) - check("bad ids are refused, not fatal", not r["ok"] and s.api("alive")["ok"], - str(r)) + check( + "bad ids are refused, not fatal", + not r["ok"] and s.api("alive")["ok"], + str(r), + ) # ---- hovering the strip ---------------------------------------------------- -ACCENT = "#ff5fd7" # the fill of the tab you are in, and hover on the rest -INK = "#141418" # text on that fill -BRIGHT = "#ffffff" # that text while the pointer is on it -DIM = "#45454a" # a tab you are not in +ACCENT = "#ff5fd7" # the fill of the tab you are in, and hover on the rest +INK = "#141418" # text on that fill +BRIGHT = "#ffffff" # that text while the pointer is on it +DIM = "#45454a" # a tab you are not in def two_tab_layout(): f = tempfile.NamedTemporaryFile("w", suffix=".kdl", delete=False) - f.write('layout {\n tab name="api" {\n pane\n }\n' - ' tab name="notes" {\n pane\n }\n}\n') + f.write( + 'layout {\n tab name="api" {\n pane\n }\n tab name="notes" {\n pane\n }\n}\n' + ) f.close() return f.name def strip_hits(snap): - return {h["action"]: h for h in snap.hits - if h["action"].startswith("tab:") or h["action"] == "newtab"} + return { + h["action"]: h + for h in snap.hits + if h["action"].startswith("tab:") or h["action"] == "newtab" + } def hover(s, x, y): @@ -294,11 +362,17 @@ def test_hovering_the_strip_lights_what_is_under_the_pointer(): # The tab you are in keeps its fill and brightens its text; the # others have no fill to keep and take the accent. want = BRIGHT if target == active_id else ACCENT - check(f"hovering {target} lights it", - fg_of(snap, h) == want, f"{fg_of(snap, h)} wanted {want}") + check( + f"hovering {target} lights it", + fg_of(snap, h) == want, + f"{fg_of(snap, h)} wanted {want}", + ) others = [(k, fg_of(snap, o)) for k, o in hits.items() if k != target] - check("and nothing else on the strip", - all(c in (INK, DIM) for _, c in others), str(others)) + check( + "and nothing else on the strip", + all(c in (INK, DIM) for _, c in others), + str(others), + ) os.unlink(lay) @@ -312,24 +386,38 @@ def test_weight_says_active_and_colour_says_pointer(): active = hits["tab:" + str(s.tabs()[0]["id"])] snap = s.snapshot() - check("the tab you are in is filled, not merely bold", - fg_of(snap, active) == INK and bg_of(snap, active) == ACCENT, - f"{fg_of(snap, active)} on {bg_of(snap, active)}") - check("and still bold", "bold" in attrs_of(snap, active), - str(attrs_of(snap, active))) + check( + "the tab you are in is filled, not merely bold", + fg_of(snap, active) == INK and bg_of(snap, active) == ACCENT, + f"{fg_of(snap, active)} on {bg_of(snap, active)}", + ) + check( + "and still bold", + "bold" in attrs_of(snap, active), + str(attrs_of(snap, active)), + ) - inactive = [h for k, h in hits.items() - if k.startswith("tab:") and h is not active][0] - check("a tab you are not in has no fill at all", - bg_of(snap, inactive) is None and fg_of(snap, inactive) == DIM, - f"{fg_of(snap, inactive)} on {bg_of(snap, inactive)}") + inactive = [ + h for k, h in hits.items() if k.startswith("tab:") and h is not active + ][0] + check( + "a tab you are not in has no fill at all", + bg_of(snap, inactive) is None and fg_of(snap, inactive) == DIM, + f"{fg_of(snap, inactive)} on {bg_of(snap, inactive)}", + ) hover(s, active["x"] + 1, active["y"]) snap = s.snapshot() - check("hovering it brightens the text", - fg_of(snap, active) == BRIGHT, str(fg_of(snap, active))) - check("but it keeps the fill that says you are in it", - bg_of(snap, active) == ACCENT, str(bg_of(snap, active))) + check( + "hovering it brightens the text", + fg_of(snap, active) == BRIGHT, + str(fg_of(snap, active)), + ) + check( + "but it keeps the fill that says you are in it", + bg_of(snap, active) == ACCENT, + str(bg_of(snap, active)), + ) os.unlink(lay) @@ -343,23 +431,29 @@ def test_leaving_the_strip_puts_it_out_and_hover_switches_nothing(): hover(s, other["x"] + 1, other["y"]) s.settle(20) - check("hovering a tab does not switch to it", - [t["active"] for t in s.tabs()] == - [t["active"] for t in before], str(s.tabs())) + check( + "hovering a tab does not switch to it", + [t["active"] for t in s.tabs()] == [t["active"] for t in before], + str(s.tabs()), + ) p = s.pane() hover(s, p["content_x"] + 2, p["content_y"] + 1) snap = s.snapshot() - check("nothing on the strip is lit once the pointer leaves it", - all(fg_of(snap, h) in (INK, DIM) for h in hits.values()), - str([fg_of(snap, h) for h in hits.values()])) + check( + "nothing on the strip is lit once the pointer leaves it", + all(fg_of(snap, h) in (INK, DIM) for h in hits.values()), + str([fg_of(snap, h) for h in hits.values()]), + ) # ...but clicking still switches. s.click(other["x"] + 1, other["y"]) s.settle(20) - check("clicking the tab you lit is what switches to it", - [t["active"] for t in s.tabs()] != - [t["active"] for t in before], str(s.tabs())) + check( + "clicking the tab you lit is what switches to it", + [t["active"] for t in s.tabs()] != [t["active"] for t in before], + str(s.tabs()), + ) os.unlink(lay) @@ -402,8 +496,9 @@ def test_dragging_a_tab_reorders_the_strip(): with Session(SH, cols=80, rows=12) as s: s.settle() ids = named_tabs(s, "one", "two", "three") - check("the order to start", order(s) == ["", "one", "two", "three"], - str(order(s))) + check( + "the order to start", order(s) == ["", "one", "two", "three"], str(order(s)) + ) hits = strip_hits(s.snapshot()) src = hits[f"tab:{ids[3]}"] @@ -412,8 +507,11 @@ def test_dragging_a_tab_reorders_the_strip(): motion(s, dst["x"] + 1, dst["y"]) release(s, dst["x"] + 1, dst["y"]) - check("the dragged tab lands where it was dropped", - order(s) == ["", "three", "one", "two"], str(order(s))) + check( + "the dragged tab lands where it was dropped", + order(s) == ["", "three", "one", "two"], + str(order(s)), + ) check("and you are still in it", active(s) == "three", active(s)) @@ -427,8 +525,11 @@ def test_dragging_past_the_last_tab_puts_it_last(): press(s, src["x"] + 1, src["y"]) motion(s, end["x"] + 1, end["y"]) release(s, end["x"] + 1, end["y"]) - check("dropping past the end means last", - order(s) == ["one", "two", ""], str(order(s))) + check( + "dropping past the end means last", + order(s) == ["one", "two", ""], + str(order(s)), + ) def test_dragging_off_the_strip_moves_nothing(): @@ -442,8 +543,7 @@ def test_dragging_off_the_strip_moves_nothing(): press(s, src["x"] + 1, src["y"]) motion(s, 20, 6) # into a pane, well below the strip release(s, 20, 6) - check("the order is untouched", order(s) == ["", "one", "two"], - str(order(s))) + check("the order is untouched", order(s) == ["", "one", "two"], str(order(s))) def test_a_click_that_never_moves_only_switches(): @@ -470,15 +570,17 @@ def test_move_tab_over_the_api_keeps_you_where_you_are(): r = s.api("move-tab", id=ids[3], index=1) # move "c" to the front check("the api reports ok", r.get("ok") is True, str(r)) - check("the strip is reordered", order(s) == ["c", "", "a", "b"], - str(order(s))) - check("and you are still in the tab you were in", active(s) == "a", - active(s)) + check("the strip is reordered", order(s) == ["c", "", "a", "b"], str(order(s))) + check("and you are still in the tab you were in", active(s) == "a", active(s)) - check("an unknown tab is refused", - s.api("move-tab", id=999, index=1).get("ok") is False) - check("and so is an index off the end", - s.api("move-tab", id=ids[0], index=99).get("ok") is False) + check( + "an unknown tab is refused", + s.api("move-tab", id=999, index=1).get("ok") is False, + ) + check( + "and so is an index off the end", + s.api("move-tab", id=ids[0], index=99).get("ok") is False, + ) def test_double_clicking_a_tab_renames_it(): @@ -494,18 +596,26 @@ def test_double_clicking_a_tab_renames_it(): dbl(s, strip_hits(s.snapshot())[tab2]) snap = s.snapshot() - check("the editor opens in the tab's own cell", - "\u2588" in snap.line(1), repr(snap.line(1))) + check( + "the editor opens in the tab's own cell", + "\u2588" in snap.line(1), + repr(snap.line(1)), + ) s.send("logs") - check("typing goes into it", "logs\u2588" in s.snapshot().line(1), - repr(s.snapshot().line(1))) + check( + "typing goes into it", + "logs\u2588" in s.snapshot().line(1), + repr(s.snapshot().line(1)), + ) s.send(r"\r") - check("enter keeps it", s.tabs()[1]["name"] == "logs", - str(s.tabs())) - check("and the strip shows it", "2:logs" in s.snapshot().line(1), - repr(s.snapshot().line(1))) + check("enter keeps it", s.tabs()[1]["name"] == "logs", str(s.tabs())) + check( + "and the strip shows it", + "2:logs" in s.snapshot().line(1), + repr(s.snapshot().line(1)), + ) def test_escape_abandons_and_clicking_away_keeps(): @@ -518,23 +628,24 @@ def test_escape_abandons_and_clicking_away_keeps(): dbl(s, strip_hits(s.snapshot())[tab2]) s.send("throwaway") s.send(r"\x1b") - check("escape leaves the name alone", s.tabs()[1]["name"] == "", - str(s.tabs())) + check("escape leaves the name alone", s.tabs()[1]["name"] == "", str(s.tabs())) check("and closes the editor", "\u2588" not in s.snapshot().screen()) dbl(s, strip_hits(s.snapshot())[tab2]) s.send("kept") s.click(2, 6) # somewhere that is not this tab - check("clicking away commits, the way leaving a field does", - s.tabs()[1]["name"] == "kept", str(s.tabs())) + check( + "clicking away commits, the way leaving a field does", + s.tabs()[1]["name"] == "kept", + str(s.tabs()), + ) # And an empty name gives the tab back to its number. dbl(s, strip_hits(s.snapshot())[tab2]) for _ in range(len("kept")): s.send(r"\x7f") s.send(r"\r") - check("an empty name clears it", s.tabs()[1]["name"] == "", - str(s.tabs())) + check("an empty name clears it", s.tabs()[1]["name"] == "", str(s.tabs())) def test_a_tab_and_a_pane_with_the_same_id_are_not_the_same_thing(): @@ -546,26 +657,33 @@ def test_a_tab_and_a_pane_with_the_same_id_are_not_the_same_thing(): with Session(titled, cols=70, rows=12) as s: s.settle() s.key("c") - s.until(lambda sn: any(h["action"].startswith("panetitle:") - for h in sn.hits)) + s.until(lambda sn: any(h["action"].startswith("panetitle:") for h in sn.hits)) snap = s.snapshot() pane_id = s.focused()["id"] # the new tab's pane, not tab 1's tab_id = s.tabs()[1]["id"] - check("the ids really do collide", pane_id == tab_id, - f"pane {pane_id} tab {tab_id}") + check( + "the ids really do collide", + pane_id == tab_id, + f"pane {pane_id} tab {tab_id}", + ) - title = [h for h in snap.hits - if h["action"] == f"panetitle:{pane_id}"][0] + title = [h for h in snap.hits if h["action"] == f"panetitle:{pane_id}"][0] strip = strip_hits(snap)[f"tab:{tab_id}"] s.click(strip["x"] + 1, strip["y"]) s.click(title["x"] + 1, title["y"]) - check("one click on each opens no editor", - "\u2588" not in s.snapshot().screen(), s.snapshot().screen()) + check( + "one click on each opens no editor", + "\u2588" not in s.snapshot().screen(), + s.snapshot().screen(), + ) dbl(s, title) - check("but two on the pane still open its own", - "\u2588" in s.snapshot().screen(), s.snapshot().screen()) + check( + "but two on the pane still open its own", + "\u2588" in s.snapshot().screen(), + s.snapshot().screen(), + ) if __name__ == "__main__": |
