Satisfy project quality gates

This commit is contained in:
2026-06-10 18:23:52 -04:00
parent 9379e1523c
commit a7851e7007
25 changed files with 1055 additions and 375 deletions
+168 -88
View File
@@ -15,10 +15,14 @@ def runner():
@pytest.mark.asyncio
async def test_new_window(runner):
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows -F '#{window_index}' (next-index query)
(0, "0\n", ""), # list-windows -F '#{window_index}' (next-index query)
(0, "@1\n", ""), # new-window -P -F
(0, "", ""), # set-option (combined: remain-on-exit + allow-rename + automatic-rename)
(0, "", ""), # respawn-pane
(
0,
"",
"",
), # set-option (combined: remain-on-exit + allow-rename + automatic-rename)
(0, "", ""), # respawn-pane
]
result = await runner.new_window("hqt-1", "/tmp", "kiro-cli chat")
assert result == "@1"
@@ -26,9 +30,23 @@ async def test_new_window(runner):
# options, then the per-window Frappé theme options (see new_window).
set_call = runner._exec.call_args_list[2].args
assert set_call[:17] == (
"set-option", "-t", "hqt-main:=hqt-1", "remain-on-exit", "on",
";", "set-option", "-t", "hqt-main:=hqt-1", "allow-rename", "off",
";", "set-option", "-t", "hqt-main:=hqt-1", "automatic-rename", "off",
"set-option",
"-t",
"hqt-main:=hqt-1",
"remain-on-exit",
"on",
";",
"set-option",
"-t",
"hqt-main:=hqt-1",
"allow-rename",
"off",
";",
"set-option",
"-t",
"hqt-main:=hqt-1",
"automatic-rename",
"off",
)
assert "window-status-current-style" in set_call
# respawn-pane runs the actual command
@@ -51,9 +69,9 @@ async def test_new_window_targets_explicit_free_index(runner):
and targeting `session:idx` sidesteps the resolution entirely."""
runner._exec.side_effect = [
(0, "0\n1\n2\n", ""), # list-windows -F '#{window_index}' (existing: 0,1,2)
(0, "@9\n", ""), # new-window -P -F -> window_id
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
(0, "@9\n", ""), # new-window -P -F -> window_id
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
]
window_id = await runner.new_window("hqt-9", "/tmp", "claude")
assert window_id == "@9"
@@ -61,7 +79,9 @@ async def test_new_window_targets_explicit_free_index(runner):
new_win_args = runner._exec.call_args_list[1].args
assert "new-window" in new_win_args
# Explicit free index = max(0,1,2)+1 = 3, targeted as "<session>:3".
assert "hqt-main:3" in new_win_args, f"expected explicit free index target, got {new_win_args}"
assert "hqt-main:3" in new_win_args, (
f"expected explicit free index target, got {new_win_args}"
)
# Must NOT use the bare session name as the new-window target.
ti = new_win_args.index("-t")
assert new_win_args[ti + 1] == "hqt-main:3"
@@ -102,7 +122,13 @@ async def test_select_window(runner):
async def test_respawn_pane(runner):
await runner.respawn_pane("hqt-1", "kiro-cli chat", "/tmp")
runner._exec.assert_called_with(
"respawn-pane", "-t", "hqt-main:=hqt-1", "-k", "-c", "/tmp", "kiro-cli chat",
"respawn-pane",
"-t",
"hqt-main:=hqt-1",
"-k",
"-c",
"/tmp",
"kiro-cli chat",
)
@@ -116,10 +142,10 @@ async def test_list_windows(runner):
@pytest.mark.asyncio
async def test_manager_spawn(runner):
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "@3\n", ""), # new-window -P -F
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
(0, "0\n", ""), # list-windows (next-index query)
(0, "@3\n", ""), # new-window -P -F
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
]
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
mgr = TmuxManager(runner)
@@ -150,7 +176,7 @@ async def test_manager_is_alive_true(runner):
# is_pane_dead needs list-panes to return "0"
runner._exec.side_effect = [
(0, "hqt-1\n", ""), # has_window
(0, "0\n", ""), # is_pane_dead
(0, "0\n", ""), # is_pane_dead
]
mgr = TmuxManager(runner)
assert await mgr.is_alive("hqt-1") is True
@@ -161,7 +187,7 @@ async def test_manager_is_alive_false_dead_pane(runner):
"""Window exists but pane dead."""
runner._exec.side_effect = [
(0, "hqt-1\n", ""), # has_window
(0, "1\n", ""), # is_pane_dead
(0, "1\n", ""), # is_pane_dead
]
mgr = TmuxManager(runner)
assert await mgr.is_alive("hqt-1") is False
@@ -199,10 +225,10 @@ async def test_has_window_false_single_exec(runner):
async def test_new_window_race_free_sequence(runner):
"""new_window issues: new-window (no command), set-option, respawn-pane — in order."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "@5\n", ""), # new-window -P -F -> window_id
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
(0, "0\n", ""), # list-windows (next-index query)
(0, "@5\n", ""), # new-window -P -F -> window_id
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
]
window_id = await runner.new_window("hqt-1", "/tmp", "kiro-cli chat")
assert window_id == "@5"
@@ -226,9 +252,23 @@ async def test_new_window_race_free_sequence(runner):
set_args = calls[2].args
assert set_args[:17] == (
"set-option", "-t", "hqt-main:=hqt-1", "remain-on-exit", "on",
";", "set-option", "-t", "hqt-main:=hqt-1", "allow-rename", "off",
";", "set-option", "-t", "hqt-main:=hqt-1", "automatic-rename", "off",
"set-option",
"-t",
"hqt-main:=hqt-1",
"remain-on-exit",
"on",
";",
"set-option",
"-t",
"hqt-main:=hqt-1",
"allow-rename",
"off",
";",
"set-option",
"-t",
"hqt-main:=hqt-1",
"automatic-rename",
"off",
)
# Window-scoped theme options painted on this window, current-style = peach.
assert "window-status-current-style" in set_args
@@ -249,12 +289,14 @@ async def test_new_window_race_free_sequence(runner):
async def test_new_window_with_env(runner):
"""new_window passes -e KEY=VALUE flags on respawn-pane (step 3), NOT on new-window (step 1)."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "0\n", ""), # list-windows (next-index query)
(0, "@7\n", ""),
(0, "", ""),
(0, "", ""),
]
window_id = await runner.new_window("hqt-2", "/tmp", "claude", env={"FOO": "bar", "X": "1"})
window_id = await runner.new_window(
"hqt-2", "/tmp", "claude", env={"FOO": "bar", "X": "1"}
)
assert window_id == "@7"
calls_list = runner._exec.call_args_list
@@ -278,10 +320,10 @@ async def test_new_window_with_env(runner):
async def test_new_window_set_option_failure_aborts(runner):
"""If set-option fails, new_window kills the orphaned window and returns None."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "@3\n", ""), # new-window succeeds
(0, "0\n", ""), # list-windows (next-index query)
(0, "@3\n", ""), # new-window succeeds
(1, "", "some tmux error"), # set-option fails
(0, "", ""), # kill-window cleanup
(0, "", ""), # kill-window cleanup
]
result = await runner.new_window("hqt-3", "/tmp", "claude")
assert result is None
@@ -296,11 +338,11 @@ async def test_new_window_set_option_failure_aborts(runner):
async def test_new_window_respawn_failure_kills_window(runner):
"""If respawn-pane fails, new_window kills the orphaned window and returns None."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "@4\n", ""), # new-window succeeds
(0, "", ""), # set-option succeeds
(1, "", "respawn error"), # respawn-pane fails
(0, "", ""), # kill-window cleanup
(0, "0\n", ""), # list-windows (next-index query)
(0, "@4\n", ""), # new-window succeeds
(0, "", ""), # set-option succeeds
(1, "", "respawn error"), # respawn-pane fails
(0, "", ""), # kill-window cleanup
]
result = await runner.new_window("hqt-4", "/tmp", "claude")
assert result is None
@@ -314,7 +356,7 @@ async def test_new_window_respawn_failure_kills_window(runner):
async def test_new_window_returns_window_id(runner):
"""new_window returns the window_id string on full success."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "0\n", ""), # list-windows (next-index query)
(0, "@42\n", ""),
(0, "", ""),
(0, "", ""),
@@ -327,7 +369,7 @@ async def test_new_window_returns_window_id(runner):
async def test_new_window_returns_none_on_new_window_failure(runner):
"""new_window returns None when the initial new-window command fails."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "0\n", ""), # list-windows (next-index query)
(1, "", "session not found"), # new-window fails
]
result = await runner.new_window("hqt-x", "/tmp", "sleep 1")
@@ -354,8 +396,8 @@ async def test_verify_window_alive_alive(runner):
async def test_verify_window_alive_dead_with_text(runner):
"""Pane dies within timeout → (False, captured text)."""
runner._exec.side_effect = [
(0, "1\n", ""), # is_pane_dead → dead
(0, "command not found\n", ""), # capture_pane
(0, "1\n", ""), # is_pane_dead → dead
(0, "command not found\n", ""), # capture_pane
]
ok, text = await runner.verify_window_alive("hqt-1", timeout=0.5, interval=0.05)
assert ok is False
@@ -366,8 +408,8 @@ async def test_verify_window_alive_dead_with_text(runner):
async def test_verify_window_alive_dead_empty_text(runner):
"""Pane dies with empty capture → (False, '') still False."""
runner._exec.side_effect = [
(0, "1\n", ""), # is_pane_dead
(0, "", ""), # capture_pane empty
(0, "1\n", ""), # is_pane_dead
(0, "", ""), # capture_pane empty
]
ok, text = await runner.verify_window_alive("hqt-1", timeout=0.5, interval=0.05)
assert ok is False
@@ -379,9 +421,9 @@ async def test_verify_window_alive_dies_during_last_interval(runner):
"""Pane alive throughout polling loop but dies during the final sleep → (False, text)."""
# All in-loop polls return alive (0), final post-loop check returns dead (1)
runner._exec.side_effect = [
(0, "0\n", ""), # is_pane_dead (in-loop) → alive
(0, "1\n", ""), # is_pane_dead (post-loop final check) → dead
(0, "died at last\n", ""), # capture_pane
(0, "0\n", ""), # is_pane_dead (in-loop) → alive
(0, "1\n", ""), # is_pane_dead (post-loop final check) → dead
(0, "died at last\n", ""), # capture_pane
]
ok, text = await runner.verify_window_alive("hqt-1", timeout=0.05, interval=0.1)
assert ok is False
@@ -397,10 +439,10 @@ async def test_verify_window_alive_dies_during_last_interval(runner):
async def test_spawn_result_ok(runner):
"""spawn returns SpawnResult(ok=True, window_id=...) on success."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "@10\n", ""), # new-window
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
(0, "0\n", ""), # list-windows (next-index query)
(0, "@10\n", ""), # new-window
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
]
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
mgr = TmuxManager(runner)
@@ -416,10 +458,10 @@ async def test_spawn_result_ok(runner):
async def test_spawn_result_failure_verification(runner):
"""spawn returns SpawnResult(ok=False, error=...) when pane dies immediately."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "@11\n", ""), # new-window
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
(0, "0\n", ""), # list-windows (next-index query)
(0, "@11\n", ""), # new-window
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
]
runner.verify_window_alive = AsyncMock(return_value=(False, "no such binary\n"))
mgr = TmuxManager(runner)
@@ -433,14 +475,16 @@ async def test_spawn_result_failure_verification(runner):
async def test_spawn_shlex_join(runner):
"""spawn uses shlex.join so args with spaces are properly quoted."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "0\n", ""), # list-windows (next-index query)
(0, "@99\n", ""),
(0, "", ""),
(0, "", ""),
]
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
mgr = TmuxManager(runner)
req = SpawnRequest(window_name="hqt-1", command=["my cmd", "arg with space"], cwd="/tmp")
req = SpawnRequest(
window_name="hqt-1", command=["my cmd", "arg with space"], cwd="/tmp"
)
await mgr.spawn(req)
# The respawn-pane call (now the 4th _exec call) should have the shlex-joined string
@@ -449,6 +493,7 @@ async def test_spawn_shlex_join(runner):
joined_cmd = respawn_call[-1]
# shlex.join of ["my cmd", "arg with space"] → "'my cmd' 'arg with space'"
import shlex
expected = shlex.join(["my cmd", "arg with space"])
assert joined_cmd == expected
@@ -457,14 +502,16 @@ async def test_spawn_shlex_join(runner):
async def test_spawn_passes_env(runner):
"""spawn passes env dict through to respawn-pane (step 3), NOT to new-window (step 1)."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "0\n", ""), # list-windows (next-index query)
(0, "@20\n", ""),
(0, "", ""),
(0, "", ""),
]
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
mgr = TmuxManager(runner)
req = SpawnRequest(window_name="hqt-env", command=["bash"], cwd="/tmp", env={"MY_VAR": "hello"})
req = SpawnRequest(
window_name="hqt-env", command=["bash"], cwd="/tmp", env={"MY_VAR": "hello"}
)
result = await mgr.spawn(req)
assert result.ok is True
@@ -480,11 +527,12 @@ async def test_spawn_passes_env(runner):
assert "MY_VAR=hello" in respawn_args
@pytest.mark.asyncio
async def test_respawn_pane_with_env(runner):
"""respawn_pane passes -e KEY=VALUE flags when env is provided."""
await runner.respawn_pane("hqt-1", "claude", "/tmp", env={"MYKEY": "myval", "A": "b"})
await runner.respawn_pane(
"hqt-1", "claude", "/tmp", env={"MYKEY": "myval", "A": "b"}
)
call_args = runner._exec.call_args_list[0].args
assert "respawn-pane" in call_args
assert "-e" in call_args
@@ -501,15 +549,14 @@ async def test_respawn_pane_no_env(runner):
assert "-e" not in call_args
@pytest.mark.asyncio
async def test_spawn_verify_dead_empty_text_returns_fallback_error(runner):
"""When verify_window_alive returns (False, ''), spawn returns the fallback error message."""
runner._exec.side_effect = [
(0, "0\n", ""), # list-windows (next-index query)
(0, "@30\n", ""), # new-window
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
(0, "0\n", ""), # list-windows (next-index query)
(0, "@30\n", ""), # new-window
(0, "", ""), # set-option
(0, "", ""), # respawn-pane
]
# Empty pane text — verify_window_alive signals immediate death with no output
runner.verify_window_alive = AsyncMock(return_value=(False, ""))
@@ -531,7 +578,7 @@ async def test_respawn_verified_success_path(runner):
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
runner._exec.side_effect = [
(0, "hqt-1\n", ""), # has_window → True
(0, "", ""), # respawn-pane
(0, "", ""), # respawn-pane
]
mgr = TmuxManager(runner)
result = await mgr.respawn_verified("hqt-1", ["claude", "--resume", "abc"], "/tmp")
@@ -545,7 +592,7 @@ async def test_respawn_verified_pane_dies_after_respawn(runner):
runner.verify_window_alive = AsyncMock(return_value=(False, "no such transcript\n"))
runner._exec.side_effect = [
(0, "hqt-1\n", ""), # has_window → True
(0, "", ""), # respawn-pane
(0, "", ""), # respawn-pane
]
mgr = TmuxManager(runner)
result = await mgr.respawn_verified("hqt-1", ["claude", "--resume", "abc"], "/tmp")
@@ -559,7 +606,7 @@ async def test_respawn_verified_pane_dies_empty_text_fallback_message(runner):
runner.verify_window_alive = AsyncMock(return_value=(False, ""))
runner._exec.side_effect = [
(0, "hqt-1\n", ""), # has_window → True
(0, "", ""), # respawn-pane
(0, "", ""), # respawn-pane
]
mgr = TmuxManager(runner)
result = await mgr.respawn_verified("hqt-1", ["claude", "--resume", "abc"], "/tmp")
@@ -572,14 +619,16 @@ async def test_respawn_verified_window_gone_branch(runner):
"""respawn_verified: window is gone → new_window + verify → SpawnResult(ok=True, window_id=...)."""
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
runner._exec.side_effect = [
(0, "other\n", ""), # has_window → False
(0, "0\n", ""), # new_window: list-windows (next-index query)
(0, "@9\n", ""), # new-window step 1
(0, "", ""), # set-option step 2
(0, "", ""), # respawn-pane step 3
(0, "other\n", ""), # has_window → False
(0, "0\n", ""), # new_window: list-windows (next-index query)
(0, "@9\n", ""), # new-window step 1
(0, "", ""), # set-option step 2
(0, "", ""), # respawn-pane step 3
]
mgr = TmuxManager(runner)
result = await mgr.respawn_verified("hqt-gone", ["claude", "--resume", "abc"], "/tmp")
result = await mgr.respawn_verified(
"hqt-gone", ["claude", "--resume", "abc"], "/tmp"
)
assert result.ok is True
assert result.window_id == "@9"
@@ -588,12 +637,14 @@ async def test_respawn_verified_window_gone_branch(runner):
async def test_respawn_verified_window_gone_new_window_fails(runner):
"""respawn_verified: window gone, new_window fails → SpawnResult(ok=False)."""
runner._exec.side_effect = [
(0, "other\n", ""), # has_window → False
(0, "0\n", ""), # new_window: list-windows (next-index query)
(1, "", "session not found"), # new-window fails
(0, "other\n", ""), # has_window → False
(0, "0\n", ""), # new_window: list-windows (next-index query)
(1, "", "session not found"), # new-window fails
]
mgr = TmuxManager(runner)
result = await mgr.respawn_verified("hqt-gone", ["claude", "--resume", "abc"], "/tmp")
result = await mgr.respawn_verified(
"hqt-gone", ["claude", "--resume", "abc"], "/tmp"
)
assert result.ok is False
assert result.error != ""
@@ -604,7 +655,7 @@ async def test_respawn_verified_forwards_env(runner):
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
runner._exec.side_effect = [
(0, "hqt-1\n", ""), # has_window → True
(0, "", ""), # respawn-pane
(0, "", ""), # respawn-pane
]
mgr = TmuxManager(runner)
result = await mgr.respawn_verified(
@@ -621,10 +672,11 @@ async def test_respawn_verified_forwards_env(runner):
async def test_respawn_verified_uses_shlex_join(runner):
"""respawn_verified uses shlex.join so args with spaces are properly quoted."""
import shlex
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
runner._exec.side_effect = [
(0, "hqt-1\n", ""), # has_window → True
(0, "", ""), # respawn-pane
(0, "", ""), # respawn-pane
]
mgr = TmuxManager(runner)
await mgr.respawn_verified("hqt-1", ["cmd with space", "arg"], "/tmp")
@@ -641,13 +693,15 @@ async def test_respawn_verified_window_gone_forwards_env(runner):
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
runner._exec.side_effect = [
(0, "other-window\n", ""), # has_window → False
(0, "0\n", ""), # new_window: list-windows (next-index query)
(0, "@5\n", ""), # new-window step 1
(0, "", ""), # set-option step 2
(0, "", ""), # respawn-pane step 3
(0, "0\n", ""), # new_window: list-windows (next-index query)
(0, "@5\n", ""), # new-window step 1
(0, "", ""), # set-option step 2
(0, "", ""), # respawn-pane step 3
]
mgr = TmuxManager(runner)
result = await mgr.respawn_verified("hqt-gone", ["bash"], "/tmp", env={"GONE_VAR": "x"})
result = await mgr.respawn_verified(
"hqt-gone", ["bash"], "/tmp", env={"GONE_VAR": "x"}
)
assert result.ok is True
# The respawn-pane (step 3 of new_window) should carry the env
@@ -667,7 +721,12 @@ async def test_send_text_uses_literal_flag(runner):
"""send_text must pass -l and '--' before the text."""
await runner.send_text("hqt-1", "Enter; echo hi C-c")
runner._exec.assert_called_once_with(
"send-keys", "-t", "hqt-main:=hqt-1", "-l", "--", "Enter; echo hi C-c",
"send-keys",
"-t",
"hqt-main:=hqt-1",
"-l",
"--",
"Enter; echo hi C-c",
)
@@ -676,7 +735,10 @@ async def test_send_enter_no_literal_flag(runner):
"""send_enter intentionally sends the Enter key name — must NOT have -l."""
await runner.send_enter("hqt-1")
runner._exec.assert_called_once_with(
"send-keys", "-t", "hqt-main:=hqt-1", "Enter",
"send-keys",
"-t",
"hqt-main:=hqt-1",
"Enter",
)
@@ -690,11 +752,16 @@ async def test_list_windows_info_alive_and_activity(runner):
"""list_windows_info parses 3-column output; alive=True when pane_dead=0."""
runner._exec.return_value = (0, "hqt-1\t0\t1000000\nhqt-2\t1\t999999\n", "")
from hqt.tmux.runner import WindowInfo
result = await runner.list_windows_info()
assert result["hqt-1"] == WindowInfo(alive=True, last_activity=1000000)
assert result["hqt-2"] == WindowInfo(alive=False, last_activity=999999)
runner._exec.assert_called_once_with(
"list-panes", "-s", "-t", "hqt-main", "-F",
"list-panes",
"-s",
"-t",
"hqt-main",
"-F",
"#{window_name}\t#{pane_dead}\t#{window_activity}",
)
@@ -708,6 +775,7 @@ async def test_list_windows_info_multi_pane_alive_max_activity(runner):
"",
)
from hqt.tmux.runner import WindowInfo
result = await runner.list_windows_info()
assert result["hqt-1"] == WindowInfo(alive=True, last_activity=2000)
assert result["hqt-2"] == WindowInfo(alive=False, last_activity=600)
@@ -747,7 +815,12 @@ async def test_send_text_includes_double_dash(runner):
"""send_text must pass '--' before the text to prevent flag parsing."""
await runner.send_text("hqt-1", "-flag-looking text")
runner._exec.assert_called_once_with(
"send-keys", "-t", "hqt-main:=hqt-1", "-l", "--", "-flag-looking text",
"send-keys",
"-t",
"hqt-main:=hqt-1",
"-l",
"--",
"-flag-looking text",
)
@@ -756,7 +829,12 @@ async def test_send_text_double_dash_with_normal_text(runner):
"""send_text includes '--' even for normal text (belt-and-suspenders)."""
await runner.send_text("hqt-1", "hello world")
runner._exec.assert_called_once_with(
"send-keys", "-t", "hqt-main:=hqt-1", "-l", "--", "hello world",
"send-keys",
"-t",
"hqt-main:=hqt-1",
"-l",
"--",
"hello world",
)
@@ -765,6 +843,7 @@ async def test_poll_info_single_exec(runner):
"""poll_info calls _exec exactly once and returns WindowInfo per name."""
runner._exec.return_value = (0, "hqt-1\t0\t1234\nhqt-2\t1\t5678\n", "")
from hqt.tmux.runner import WindowInfo
mgr = TmuxManager(runner)
result = await mgr.poll_info(["hqt-1", "hqt-2", "hqt-missing"])
assert runner._exec.call_count == 1
@@ -881,7 +960,8 @@ async def test_apply_theme_paints_window_options_on_every_window(runner):
for window in ("⌂ HQT", "hqt-1", "hqt-2"):
target = f"hqt-main:={window}"
idxs = [
i for i, a in enumerate(args)
i
for i, a in enumerate(args)
if a == "window-status-current-style" and args[i - 1] == target
]
assert idxs, f"no current-style set for {window}"