Files
wehub-resource-sync 0418dc5cf9
CI / Python tests (3.10) (push) Has been cancelled
CI / Python tests (3.11) (push) Has been cancelled
CI / Python quality (push) Has been cancelled
CI / Frontend typecheck (push) Has been cancelled
Autopilot Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:45:00 +08:00

185 lines
6.3 KiB
Python

"""Tests for the Textual terminal UI."""
from __future__ import annotations
import pytest
from openharness.api.client import ApiMessageCompleteEvent
from openharness.api.usage import UsageSnapshot
from openharness.engine.messages import ConversationMessage, TextBlock, ToolUseBlock
from openharness.ui.textual_app import OpenHarnessTerminalApp
class StaticApiClient:
"""Fake streaming client for UI tests."""
def __init__(self, text: str) -> None:
self._text = text
async def stream_message(self, request):
del request
yield ApiMessageCompleteEvent(
message=ConversationMessage(role="assistant", content=[TextBlock(text=self._text)]),
usage=UsageSnapshot(input_tokens=2, output_tokens=3),
stop_reason=None,
)
class ScriptedApiClient:
"""Fake client that yields a scripted sequence of assistant turns."""
def __init__(self, messages) -> None:
self._messages = list(messages)
async def stream_message(self, request):
del request
message = self._messages.pop(0)
yield ApiMessageCompleteEvent(
message=message,
usage=UsageSnapshot(input_tokens=2, output_tokens=3),
stop_reason=None,
)
@pytest.mark.asyncio
async def test_textual_app_handles_commands(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
app = OpenHarnessTerminalApp(api_client=StaticApiClient("unused"))
async with app.run_test() as pilot:
composer = app.query_one("#composer")
composer.value = "/version"
await pilot.press("enter")
await pilot.pause()
assert any("OpenHarness" in line for line in app.transcript_lines)
@pytest.mark.asyncio
async def test_textual_app_runs_one_model_turn(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
app = OpenHarnessTerminalApp(api_client=StaticApiClient("hello from textual"))
async with app.run_test() as pilot:
composer = app.query_one("#composer")
composer.value = "hi"
await pilot.press("enter")
await pilot.pause()
assert any("user> hi" in line for line in app.transcript_lines)
assert any("assistant> hello from textual" in line for line in app.transcript_lines)
@pytest.mark.asyncio
async def test_textual_app_handles_ask_user_tool(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
app = OpenHarnessTerminalApp(
api_client=ScriptedApiClient(
[
ConversationMessage(
role="assistant",
content=[
ToolUseBlock(
id="toolu_ask",
name="ask_user_question",
input={"question": "Pick a color"},
)
],
),
ConversationMessage(
role="assistant",
content=[TextBlock(text="chosen green")],
),
]
)
)
async def _answer(question: str) -> str:
assert question == "Pick a color"
return "green"
app._ask_question = _answer
async with app.run_test() as pilot:
composer = app.query_one("#composer")
composer.value = "hi"
await pilot.press("enter")
await pilot.pause()
assert any("tool-result> ask_user_question: green" in line for line in app.transcript_lines)
assert any("assistant> chosen green" in line for line in app.transcript_lines)
@pytest.mark.asyncio
async def test_textual_sidebar_refresh_is_snapshot_based(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
app = OpenHarnessTerminalApp(api_client=StaticApiClient("hello"))
async with app.run_test():
status_bar = app.query_one("#status-bar")
tasks_panel = app.query_one("#tasks-panel")
status_updates: list[str] = []
task_updates: list[str] = []
original_status_update = status_bar.update
original_task_update = tasks_panel.update
def _tracked_status_update(renderable):
status_updates.append(str(renderable))
return original_status_update(renderable)
def _tracked_task_update(renderable):
task_updates.append(str(renderable))
return original_task_update(renderable)
monkeypatch.setattr(status_bar, "update", _tracked_status_update)
monkeypatch.setattr(tasks_panel, "update", _tracked_task_update)
app._refresh_sidebars()
app._refresh_sidebars()
assert status_updates == []
assert task_updates == []
app._bundle.app_state.set(model="gpt-5.4")
app._refresh_sidebars()
assert len(status_updates) == 1
assert task_updates == []
app._refresh_sidebars()
assert len(status_updates) == 1
assert task_updates == []
@pytest.mark.asyncio
async def test_textual_current_response_update_is_deduplicated(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
app = OpenHarnessTerminalApp(api_client=StaticApiClient("hello"))
async with app.run_test():
current_response = app.query_one("#current-response")
updates: list[str] = []
original_update = current_response.update
def _tracked_update(renderable):
updates.append(str(renderable))
return original_update(renderable)
monkeypatch.setattr(current_response, "update", _tracked_update)
app._set_current_response("same message")
app._set_current_response("same message")
app._set_current_response("next message")
assert updates == ["same message", "next message"]