Merge branch 'tool-windows'
Add tool windows: tool registry, styled aux tmux windows (TmuxRunner.new_aux_window / TmuxManager.open_aux_window), SessionService tool-window + clone-for-window methods, and hqt tool / hqt palette CLI subcommands (fzf tool launcher). # Conflicts: # src/hqt/cli.py # src/hqt/tmux/manager.py # tests/test_cli.py # tests/test_sessions.py
This commit is contained in:
+111
-7
@@ -2,9 +2,8 @@ from unittest.mock import patch
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from hqt import cli as cli_module
|
||||
from hqt import sandbox
|
||||
from hqt.cli import main
|
||||
from hqt import cli, sandbox
|
||||
from hqt.config import Settings
|
||||
|
||||
|
||||
def _which(found: set[str]):
|
||||
@@ -16,10 +15,10 @@ def test_doctor_reports_bwrap_present():
|
||||
# tmux is discovered via cli.shutil.which; bwrap availability comes from the
|
||||
# shared sandbox.is_available() helper (the single source of truth).
|
||||
with (
|
||||
patch.object(cli_module.shutil, "which", side_effect=_which({"tmux", "bwrap"})),
|
||||
patch.object(cli.shutil, "which", side_effect=_which({"tmux", "bwrap"})),
|
||||
patch.object(sandbox, "is_available", return_value=True),
|
||||
):
|
||||
result = CliRunner().invoke(main, ["doctor"])
|
||||
result = CliRunner().invoke(cli.main, ["doctor"])
|
||||
assert result.exit_code == 0
|
||||
assert "bubblewrap: ✓" in result.output
|
||||
|
||||
@@ -27,9 +26,114 @@ def test_doctor_reports_bwrap_present():
|
||||
def test_doctor_reports_bwrap_missing():
|
||||
"""Test that doctor reports bubblewrap as missing when not present."""
|
||||
with (
|
||||
patch.object(cli_module.shutil, "which", side_effect=_which({"tmux"})),
|
||||
patch.object(cli.shutil, "which", side_effect=_which({"tmux"})),
|
||||
patch.object(sandbox, "is_available", return_value=False),
|
||||
):
|
||||
result = CliRunner().invoke(main, ["doctor"])
|
||||
result = CliRunner().invoke(cli.main, ["doctor"])
|
||||
assert result.exit_code == 0
|
||||
assert "bubblewrap: ✗" in result.output
|
||||
|
||||
|
||||
def test_tool_cmd_opens_tool_window(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(
|
||||
"hqt.config.get_settings", lambda: Settings(db_path=tmp_path / "t.db")
|
||||
)
|
||||
calls = {}
|
||||
|
||||
async def fake_for_window(self, window, tool):
|
||||
calls["tool"] = (window, tool)
|
||||
return "@9"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hqt.sessions.service.SessionService.open_tool_window_for_window",
|
||||
fake_for_window,
|
||||
)
|
||||
|
||||
result = CliRunner().invoke(cli.main, ["tool", "lazygit", "hqt-5"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert calls["tool"] == ("hqt-5", "lazygit")
|
||||
|
||||
|
||||
def test_tool_cmd_clone_dispatches_to_clone(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(
|
||||
"hqt.config.get_settings", lambda: Settings(db_path=tmp_path / "t.db")
|
||||
)
|
||||
calls = {}
|
||||
|
||||
async def fake_clone(self, window):
|
||||
calls["clone"] = window
|
||||
return "RESULT"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hqt.sessions.service.SessionService.clone_session_for_window", fake_clone
|
||||
)
|
||||
|
||||
result = CliRunner().invoke(cli.main, ["tool", "clone", "hqt-5"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert calls["clone"] == "hqt-5"
|
||||
|
||||
|
||||
def test_tool_cmd_reports_service_error(monkeypatch, tmp_path):
|
||||
from hqt.errors import ServiceError
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hqt.config.get_settings", lambda: Settings(db_path=tmp_path / "t.db")
|
||||
)
|
||||
|
||||
async def boom(self, window, tool):
|
||||
raise ServiceError("'hqt-5' is not an hqt session window")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hqt.sessions.service.SessionService.open_tool_window_for_window", boom
|
||||
)
|
||||
|
||||
result = CliRunner().invoke(cli.main, ["tool", "nvim", "hqt-5"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "not an hqt session window" in result.output
|
||||
|
||||
|
||||
def test_palette_cmd_shows_popup_for_session_window(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(
|
||||
"hqt.config.get_settings", lambda: Settings(db_path=tmp_path / "t.db")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hqt.sessions.service.SessionService.session_id_for_window",
|
||||
lambda self, window: 5,
|
||||
)
|
||||
runs = []
|
||||
monkeypatch.setattr(cli.subprocess, "run", lambda argv, **kw: runs.append(argv))
|
||||
|
||||
result = CliRunner().invoke(cli.main, ["palette", "hqt-5"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert len(runs) == 1
|
||||
argv = runs[0]
|
||||
assert "display-popup" in argv
|
||||
popup_cmd = argv[-1]
|
||||
assert "fzf" in popup_cmd
|
||||
assert "nvim" in popup_cmd and "clone" in popup_cmd
|
||||
# the window is baked into the command for `hqt tool {} <window>`
|
||||
assert "hqt-5" in popup_cmd
|
||||
|
||||
|
||||
def test_palette_cmd_hints_for_non_session_window(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(
|
||||
"hqt.config.get_settings", lambda: Settings(db_path=tmp_path / "t.db")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hqt.sessions.service.SessionService.session_id_for_window",
|
||||
lambda self, window: None,
|
||||
)
|
||||
runs = []
|
||||
monkeypatch.setattr(cli.subprocess, "run", lambda argv, **kw: runs.append(argv))
|
||||
|
||||
result = CliRunner().invoke(cli.main, ["palette", "nvim"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert len(runs) == 1
|
||||
argv = runs[0]
|
||||
assert "display-message" in argv
|
||||
assert "display-popup" not in argv
|
||||
|
||||
Reference in New Issue
Block a user