test_tui_state.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. """Tests for the TUI view-state reducer."""
  2. from stt_daemon.tui.state import MAX_TRANSCRIPTS, ViewState
  3. def test_mode_event_updates_mode_and_style():
  4. state = ViewState()
  5. state.ingest({"type": "mode", "mode": "command_armed"})
  6. assert state.mode == "command_armed"
  7. assert state.mode_style() == "yellow"
  8. def test_heartbeat_self_corrects_missed_mode():
  9. state = ViewState()
  10. state.ingest(
  11. {
  12. "type": "heartbeat",
  13. "mode": "chat",
  14. "stt_ok": True,
  15. "in_flight": False,
  16. "worker_stall_s": 0.0,
  17. "queue_depth": 2,
  18. "device_ok": True,
  19. }
  20. )
  21. assert state.mode == "chat"
  22. assert state.queue_depth == 2
  23. def test_health_red_when_stt_not_ok():
  24. state = ViewState()
  25. state.ingest({"type": "heartbeat", "mode": "idle", "stt_ok": False})
  26. assert state.health_ok() is False
  27. def test_health_red_when_device_down():
  28. state = ViewState()
  29. state.ingest({"type": "heartbeat", "mode": "idle", "stt_ok": True, "device_ok": False})
  30. assert state.health_ok() is False
  31. def test_worker_stall_recorded_and_degrades():
  32. state = ViewState()
  33. state.ingest({"type": "heartbeat", "stt_ok": False, "worker_stall_s": 16.5})
  34. assert state.worker_stall_s == 16.5
  35. assert state.health_ok() is False
  36. def test_command_text_and_edited_flag():
  37. state = ViewState()
  38. state.ingest({"type": "command_text", "text": "hello", "edited": True, "phase": "ready"})
  39. assert state.command_text == "hello"
  40. assert state.command_edited is True
  41. assert state.phase == "ready"
  42. def test_status_populates_fields():
  43. state = ViewState()
  44. state.ingest(
  45. {
  46. "type": "status",
  47. "mode": "command_armed",
  48. "ready": True,
  49. "phase": "ready",
  50. "command_text": "x",
  51. "stt_ok": True,
  52. }
  53. )
  54. assert state.mode == "command_armed"
  55. assert state.ready is True
  56. assert state.command_text == "x"
  57. def test_transcripts_are_bounded():
  58. state = ViewState()
  59. for i in range(MAX_TRANSCRIPTS + 25):
  60. state.ingest({"type": "transcript", "text": f"t{i}", "lang": "en"})
  61. assert len(state.transcripts) == MAX_TRANSCRIPTS
  62. assert state.transcripts[-1]["text"] == f"t{MAX_TRANSCRIPTS + 24}"
  63. assert state.transcript_lines()[-1].startswith("[en]")
  64. def test_chat_and_tool_events_recorded():
  65. state = ViewState()
  66. state.ingest({"type": "chat_message", "role": "user", "text": "hi"})
  67. state.ingest({"type": "chat_message", "role": "assistant", "text": "hello"})
  68. state.ingest({"type": "tool_call", "tool": "get_datetime", "args": {}})
  69. state.ingest({"type": "tool_result", "tool": "get_datetime", "ok": True, "result": "x"})
  70. state.ingest({"type": "error", "code": "no_viewer", "message": "m"})
  71. assert state.chat_lines() == ["user: hi", "assistant: hello"]
  72. assert state.tool is not None and state.tool["tool"] == "get_datetime"
  73. assert state.tool_result is not None and state.tool_result["ok"] is True
  74. assert state.last_error is not None and state.last_error["code"] == "no_viewer"