"""Tests for the TUI view-state reducer.""" from stt_daemon.tui.state import MAX_TRANSCRIPTS, ViewState def test_mode_event_updates_mode_and_style(): state = ViewState() state.ingest({"type": "mode", "mode": "command_armed"}) assert state.mode == "command_armed" assert state.mode_style() == "yellow" def test_heartbeat_self_corrects_missed_mode(): state = ViewState() state.ingest( { "type": "heartbeat", "mode": "chat", "stt_ok": True, "in_flight": False, "worker_stall_s": 0.0, "queue_depth": 2, "device_ok": True, } ) assert state.mode == "chat" assert state.queue_depth == 2 def test_health_red_when_stt_not_ok(): state = ViewState() state.ingest({"type": "heartbeat", "mode": "idle", "stt_ok": False}) assert state.health_ok() is False def test_health_red_when_device_down(): state = ViewState() state.ingest({"type": "heartbeat", "mode": "idle", "stt_ok": True, "device_ok": False}) assert state.health_ok() is False def test_worker_stall_recorded_and_degrades(): state = ViewState() state.ingest({"type": "heartbeat", "stt_ok": False, "worker_stall_s": 16.5}) assert state.worker_stall_s == 16.5 assert state.health_ok() is False def test_command_text_and_edited_flag(): state = ViewState() state.ingest({"type": "command_text", "text": "hello", "edited": True, "phase": "ready"}) assert state.command_text == "hello" assert state.command_edited is True assert state.phase == "ready" def test_status_populates_fields(): state = ViewState() state.ingest( { "type": "status", "mode": "command_armed", "ready": True, "phase": "ready", "command_text": "x", "stt_ok": True, } ) assert state.mode == "command_armed" assert state.ready is True assert state.command_text == "x" def test_transcripts_are_bounded(): state = ViewState() for i in range(MAX_TRANSCRIPTS + 25): state.ingest({"type": "transcript", "text": f"t{i}", "lang": "en"}) assert len(state.transcripts) == MAX_TRANSCRIPTS assert state.transcripts[-1]["text"] == f"t{MAX_TRANSCRIPTS + 24}" assert state.transcript_lines()[-1].startswith("[en]") def test_command_output_is_collected_for_display(): state = ViewState() state.ingest({"type": "tool_call", "tool": "get_datetime", "args": {}}) state.ingest( {"type": "tool_result", "tool": "get_datetime", "ok": True, "result": "2026-09-21T01:00:00"} ) state.ingest({"type": "error", "code": "not_ready", "message": "command is not ready"}) lines = state.lower_lines() assert any("tool get_datetime" in line for line in lines) assert any("ok:" in line and "2026-09-21" in line for line in lines) assert any("not_ready" in line for line in lines) def test_chat_turns_also_appear_in_lower_log(): state = ViewState() state.ingest({"type": "chat_message", "role": "user", "text": "hello"}) assert state.lower_lines() == ["user: hello"] def test_lower_log_is_bounded(): from stt_daemon.tui.state import MAX_LOWER state = ViewState() for i in range(MAX_LOWER + 20): state.ingest({"type": "error", "code": f"e{i}", "message": "m"}) assert len(state.lower_lines()) == MAX_LOWER def test_speech_activity_is_tracked(): state = ViewState() assert state.speech_open is False state.ingest({"type": "speech_start"}) assert state.speech_open is True state.ingest({"type": "speech_stop"}) assert state.speech_open is False state.ingest({"type": "status", "speech_open": True}) assert state.speech_open is True def test_chat_and_tool_events_recorded(): state = ViewState() state.ingest({"type": "chat_message", "role": "user", "text": "hi"}) state.ingest({"type": "chat_message", "role": "assistant", "text": "hello"}) state.ingest({"type": "tool_call", "tool": "get_datetime", "args": {}}) state.ingest({"type": "tool_result", "tool": "get_datetime", "ok": True, "result": "x"}) state.ingest({"type": "error", "code": "no_viewer", "message": "m"}) assert state.chat_lines() == ["user: hi", "assistant: hello"] assert state.tool is not None and state.tool["tool"] == "get_datetime" assert state.tool_result is not None and state.tool_result["ok"] is True assert state.last_error is not None and state.last_error["code"] == "no_viewer"