|
@@ -259,6 +259,133 @@ def test_typed_execute_clears_command_input(server):
|
|
|
assert observed["state_text"] == ""
|
|
assert observed["state_text"] == ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def test_chat_input_enter_activates_chat_sends_and_keeps_focus(server):
|
|
|
|
|
+ """Typing in #chat-input + Enter enters chat, sends, clears, and keeps focus."""
|
|
|
|
|
+ pytest.importorskip("textual")
|
|
|
|
|
+ from textual.widgets import Input
|
|
|
|
|
+
|
|
|
|
|
+ from stt_daemon.tui.app import AssistantTUI
|
|
|
|
|
+
|
|
|
|
|
+ path, _service = server
|
|
|
|
|
+ app = AssistantTUI(path)
|
|
|
|
|
+ sent: list[tuple] = []
|
|
|
|
|
+ observed: dict = {}
|
|
|
|
|
+
|
|
|
|
|
+ async def scenario():
|
|
|
|
|
+ async with app.run_test(size=(80, 40)) as pilot:
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ app.client.send = lambda type_, **fields: sent.append((type_, fields))
|
|
|
|
|
+ app._timer.stop()
|
|
|
|
|
+ app._drain()
|
|
|
|
|
+ chat_input = app.query_one("#chat-input", Input)
|
|
|
|
|
+ chat_input.focus()
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ for ch in "hello":
|
|
|
|
|
+ await pilot.press(ch)
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ await pilot.press("enter")
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ observed["value"] = chat_input.value
|
|
|
|
|
+ observed["focused"] = chat_input.has_focus
|
|
|
|
|
+ observed["lower_view"] = app._lower_view
|
|
|
|
|
+
|
|
|
|
|
+ asyncio.run(scenario())
|
|
|
|
|
+ assert [t for t, _ in sent] == ["chat_toggle", "chat_send"], sent
|
|
|
|
|
+ assert sent[1][1] == {"text": "hello"}
|
|
|
|
|
+ assert observed["value"] == ""
|
|
|
|
|
+ assert observed["focused"] is True
|
|
|
|
|
+ assert observed["lower_view"] == "chat"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_chat_input_from_chat_sends_without_toggle(server):
|
|
|
|
|
+ pytest.importorskip("textual")
|
|
|
|
|
+ from textual.widgets import Input
|
|
|
|
|
+
|
|
|
|
|
+ from stt_daemon.tui.app import AssistantTUI
|
|
|
|
|
+
|
|
|
|
|
+ path, _service = server
|
|
|
|
|
+ app = AssistantTUI(path)
|
|
|
|
|
+ sent: list[tuple] = []
|
|
|
|
|
+
|
|
|
|
|
+ async def scenario():
|
|
|
|
|
+ async with app.run_test(size=(80, 40)) as pilot:
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ app.client.send = lambda type_, **fields: sent.append((type_, fields))
|
|
|
|
|
+ app._timer.stop()
|
|
|
|
|
+ app._drain()
|
|
|
|
|
+ app.state.ingest({"type": "mode", "mode": "chat"})
|
|
|
|
|
+ chat_input = app.query_one("#chat-input", Input)
|
|
|
|
|
+ chat_input.focus()
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ for ch in "again":
|
|
|
|
|
+ await pilot.press(ch)
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ await pilot.press("enter")
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+
|
|
|
|
|
+ asyncio.run(scenario())
|
|
|
|
|
+ assert [t for t, _ in sent] == ["chat_send"], sent
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_chat_input_enter_activates_chat_and_sends(server):
|
|
|
|
|
+ """End-to-end: a typed chat message enters chat mode and lands in history."""
|
|
|
|
|
+ pytest.importorskip("textual")
|
|
|
|
|
+ from textual.widgets import Input
|
|
|
|
|
+
|
|
|
|
|
+ from stt_daemon.tui.app import AssistantTUI
|
|
|
|
|
+
|
|
|
|
|
+ path, service = server
|
|
|
|
|
+ app = AssistantTUI(path)
|
|
|
|
|
+ observed: dict = {}
|
|
|
|
|
+
|
|
|
|
|
+ async def scenario():
|
|
|
|
|
+ async with app.run_test(size=(80, 40)) as pilot:
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ chat_input = app.query_one("#chat-input", Input)
|
|
|
|
|
+ chat_input.focus()
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ for ch in "hello":
|
|
|
|
|
+ await pilot.press(ch)
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ await pilot.press("enter")
|
|
|
|
|
+ for _ in range(100):
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ if service.modes.mode.value == "chat" and service.chat_history:
|
|
|
|
|
+ break
|
|
|
|
|
+ observed["mode"] = service.modes.mode.value
|
|
|
|
|
+ observed["history"] = list(service.chat_history)
|
|
|
|
|
+
|
|
|
|
|
+ asyncio.run(scenario())
|
|
|
|
|
+ assert observed["mode"] == "chat"
|
|
|
|
|
+ assert observed["history"] == ["hello"]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_command_and_chat_inputs_share_a_row(server):
|
|
|
|
|
+ """#chat-input sits to the right of #command on the same row, titled 'chat'."""
|
|
|
|
|
+ pytest.importorskip("textual")
|
|
|
|
|
+ from textual.widgets import Input
|
|
|
|
|
+
|
|
|
|
|
+ from stt_daemon.tui.app import AssistantTUI
|
|
|
|
|
+
|
|
|
|
|
+ path, _service = server
|
|
|
|
|
+ app = AssistantTUI(path)
|
|
|
|
|
+ observed: dict = {}
|
|
|
|
|
+
|
|
|
|
|
+ async def scenario():
|
|
|
|
|
+ async with app.run_test(size=(80, 40)) as pilot:
|
|
|
|
|
+ await pilot.pause()
|
|
|
|
|
+ command = app.query_one("#command", Input)
|
|
|
|
|
+ chat = app.query_one("#chat-input", Input)
|
|
|
|
|
+ observed["same_row"] = command.region.y == chat.region.y
|
|
|
|
|
+ observed["chat_right"] = chat.region.x > command.region.x
|
|
|
|
|
+ observed["title"] = chat.border_title
|
|
|
|
|
+
|
|
|
|
|
+ asyncio.run(scenario())
|
|
|
|
|
+ assert observed["same_row"] is True
|
|
|
|
|
+ assert observed["chat_right"] is True
|
|
|
|
|
+ assert observed["title"] == "chat"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def test_empty_command_enter_sends_nothing(server):
|
|
def test_empty_command_enter_sends_nothing(server):
|
|
|
pytest.importorskip("textual")
|
|
pytest.importorskip("textual")
|
|
|
from textual.widgets import Input
|
|
from textual.widgets import Input
|