"""Tests for the ``recall_update`` renderer and its chat-model mapping."""
from __future__ import annotations
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from clients.terminal.tui.renderers.recall import RecallRenderer, _format_trigger
from clients.terminal.tui.themes import get_active_theme, set_active_theme
def _render_text(renderable) -> str:
console = Console(record=True, width=100, force_terminal=True, color_system=None)
console.print(renderable)
return console.export_text()
def _panel(renderable) -> Panel:
return renderable
# ── accepts ───────────────────────────────────────────────────────────────────
def test_accepts_recall_update() -> None:
set_active_theme("gnexus-dark")
renderer = RecallRenderer()
assert renderer.accepts({"type": "recall_update", "action": "scheduled"})
assert not renderer.accepts({"type": "tool_call", "tool": "schedule_recall"})
assert not renderer.accepts({"type": "status", "content": "x"})
# ── per-action rendering ─────────────────────────────────────────────────────
def test_scheduled_card_title_and_color() -> None:
set_active_theme("gnexus-dark")
theme = get_active_theme()
renderer = RecallRenderer()
msg = {
"type": "recall_update",
"action": "scheduled",
"call_type": "once",
"trigger_at": "2026-07-12T18:45:00",
}
panel = renderer.render(msg)
assert isinstance(panel, Panel)
assert "Recall scheduled" in panel.title
assert panel.border_style == theme.info.hex
text = _render_text(panel)
assert "once" in text
assert "2026-07-12 18:45:00" in text
def test_fired_card_uses_accent() -> None:
set_active_theme("gnexus-dark")
theme = get_active_theme()
renderer = RecallRenderer()
msg = {"type": "recall_update", "action": "fired", "call_type": "immediate"}
panel = renderer.render(msg)
assert "Recall fired" in panel.title
assert panel.border_style == theme.accent.hex
def test_cancelled_card_uses_error() -> None:
set_active_theme("gnexus-dark")
theme = get_active_theme()
renderer = RecallRenderer()
msg = {"type": "recall_update", "action": "cancelled", "call_type": "once"}
panel = renderer.render(msg)
assert "Recall cancelled" in panel.title
assert panel.border_style == theme.error.hex
def test_rescheduled_and_skipped_colors() -> None:
set_active_theme("gnexus-dark")
theme = get_active_theme()
renderer = RecallRenderer()
resch = renderer.render({"type": "recall_update", "action": "rescheduled"})
skipped = renderer.render({"type": "recall_update", "action": "skipped"})
assert resch.border_style == theme.info.hex
assert skipped.border_style == theme.warning.hex
def test_unknown_action_falls_back_dim() -> None:
set_active_theme("gnexus-dark")
theme = get_active_theme()
renderer = RecallRenderer()
panel = renderer.render({"type": "recall_update", "action": "weird"})
assert panel.border_style == theme.text_dim.hex
assert "weird" in panel.title
# ── trigger formatting ───────────────────────────────────────────────────────
def test_format_trigger_trims_microseconds() -> None:
assert _format_trigger("2026-07-12T18:45:00.123456") == "2026-07-12 18:45:00"
assert _format_trigger(None) == ""
assert _format_trigger("") == ""
def test_body_empty_when_no_details() -> None:
set_active_theme("gnexus-dark")
renderer = RecallRenderer()
panel = renderer.render({"type": "recall_update", "action": "fired"})
# No call_type / trigger_at → body is an empty Text; title still carries info.
assert isinstance(panel.renderable, Text)
assert panel.renderable.plain == ""
# ── chat-model mapping ───────────────────────────────────────────────────────
def test_chat_model_recall_update_creates_recall_item() -> None:
"""A live recall_update event maps to a dedicated ``recall`` ChatItem
(not the unknown-event status dump), carrying the wire fields in meta."""
from clients.terminal.tui.chat_model import ChatModel
model = ChatModel()
item = model.handle_ws_event(
{
"type": "recall_update",
"session_id": "s1",
"recall_id": "r1",
"call_type": "once",
"trigger_at": "2026-07-12T18:45:00",
"status": "pending",
"action": "scheduled",
}
)
assert item is not None
assert item.kind == "recall"
assert item.meta["action"] == "scheduled"
assert item.meta["call_type"] == "once"
assert model.items[-1] is item
def test_chat_panel_item_msg_round_trips_recall() -> None:
"""_item_msg reconstructs the recall_update wire dict from a recall item."""
from clients.terminal.tui.chat_model import ChatItem
from clients.terminal.tui.widgets.chat_panel import _item_msg
item = ChatItem(
kind="recall",
meta={"action": "fired", "call_type": "immediate", "status": "fired"},
)
msg = _item_msg(item)
assert msg["type"] == "recall_update"
assert msg["action"] == "fired"
assert msg["call_type"] == "immediate"
assert RecallRenderer().accepts(msg)