v2.15.0 (#773)
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai> Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com> Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Val <102326092+vdeva@users.noreply.github.com> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
702d0f412e
commit
cafb6d4147
247 changed files with 12401 additions and 3029 deletions
|
|
@ -1,8 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from rich.text import Text
|
||||
from textual.widgets import OptionList
|
||||
|
||||
from vibe.cli.textual_ui.widgets.session_picker import (
|
||||
SessionPickerApp,
|
||||
|
|
@ -48,6 +51,12 @@ def sample_latest_messages() -> dict[str, str]:
|
|||
}
|
||||
|
||||
|
||||
def assert_delete_state(picker: SessionPickerApp, *, kind: str, option_id: str) -> None:
|
||||
assert picker._delete_state is not None
|
||||
assert picker._delete_state.kind == kind
|
||||
assert picker._delete_state.option_id == option_id
|
||||
|
||||
|
||||
class TestFormatRelativeTime:
|
||||
def test_just_now(self) -> None:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
|
|
@ -99,6 +108,7 @@ class TestSessionPickerAppInit:
|
|||
)
|
||||
assert picker._sessions == sample_sessions
|
||||
assert picker._latest_messages == sample_latest_messages
|
||||
assert picker._current_session_id is None
|
||||
|
||||
def test_id_is_sessionpicker_app(self) -> None:
|
||||
picker = SessionPickerApp(sessions=[], latest_messages={})
|
||||
|
|
@ -107,6 +117,29 @@ class TestSessionPickerAppInit:
|
|||
def test_can_focus_children_is_true(self) -> None:
|
||||
assert SessionPickerApp.can_focus_children is True
|
||||
|
||||
def test_delete_confirmation_state_starts_empty(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
assert picker._delete_state is None
|
||||
|
||||
def test_has_sessions_tracks_session_list(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
assert picker.has_sessions is True
|
||||
|
||||
empty_picker = SessionPickerApp(sessions=[], latest_messages={})
|
||||
assert empty_picker.has_sessions is False
|
||||
|
||||
|
||||
class TestSessionPickerMessages:
|
||||
def test_session_selected_stores_option_id(self) -> None:
|
||||
|
|
@ -129,16 +162,377 @@ class TestSessionPickerMessages:
|
|||
msg = SessionPickerApp.Cancelled()
|
||||
assert isinstance(msg, SessionPickerApp.Cancelled)
|
||||
|
||||
def test_session_delete_requested_stores_session_info(self) -> None:
|
||||
msg = SessionPickerApp.SessionDeleteRequested(
|
||||
"local:test-session-id", "local", "test-session-id"
|
||||
)
|
||||
assert msg.option_id == "local:test-session-id"
|
||||
assert msg.source == "local"
|
||||
assert msg.session_id == "test-session-id"
|
||||
|
||||
|
||||
class TestSessionPickerAppBindings:
|
||||
def _get_binding_keys(self) -> list[str]:
|
||||
keys = []
|
||||
for binding in SessionPickerApp.BINDINGS:
|
||||
if isinstance(binding, tuple) and len(binding) >= 1:
|
||||
keys.append(binding[0])
|
||||
keys.extend(binding[0].split(","))
|
||||
else:
|
||||
keys.append(binding.key)
|
||||
keys.extend(binding.key.split(","))
|
||||
return keys
|
||||
|
||||
def test_has_escape_binding(self) -> None:
|
||||
assert "escape" in self._get_binding_keys()
|
||||
|
||||
def test_has_delete_binding(self) -> None:
|
||||
assert "d" in self._get_binding_keys()
|
||||
assert "D" in self._get_binding_keys()
|
||||
|
||||
|
||||
class TestSessionPickerSessionRemoval:
|
||||
def test_first_delete_request_enters_confirmation(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
|
||||
picker.action_request_delete()
|
||||
|
||||
assert_delete_state(picker, kind="confirmation", option_id="local:session-a")
|
||||
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
|
||||
assert (
|
||||
"Press D again to delete" in option_list.replaced_prompts[-1].prompt.plain
|
||||
)
|
||||
assert posted_messages == []
|
||||
|
||||
def test_second_delete_request_posts_delete_message(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
|
||||
picker.action_request_delete()
|
||||
picker.action_request_delete()
|
||||
|
||||
assert_delete_state(picker, kind="pending", option_id="local:session-a")
|
||||
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
|
||||
assert "Deleting..." in option_list.replaced_prompts[-1].prompt.plain
|
||||
assert len(posted_messages) == 1
|
||||
message = posted_messages[0]
|
||||
assert isinstance(message, SessionPickerApp.SessionDeleteRequested)
|
||||
assert message.option_id == "local:session-a"
|
||||
assert message.source == "local"
|
||||
assert message.session_id == "session-a"
|
||||
|
||||
def test_delete_confirmation_is_consumed_after_request(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
|
||||
picker.action_request_delete()
|
||||
picker.action_request_delete()
|
||||
picker.action_request_delete()
|
||||
|
||||
assert len(posted_messages) == 1
|
||||
assert_delete_state(picker, kind="pending", option_id="local:session-a")
|
||||
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
|
||||
assert "Deleting..." in option_list.replaced_prompts[-1].prompt.plain
|
||||
|
||||
def test_delete_request_shows_feedback_for_remote_sessions(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="remote:session-c")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
|
||||
picker.action_request_delete()
|
||||
|
||||
assert_delete_state(picker, kind="feedback", option_id="remote:session-c")
|
||||
assert option_list.replaced_prompts[-1].option_id == "remote:session-c"
|
||||
assert (
|
||||
"Can't delete remote session"
|
||||
in option_list.replaced_prompts[-1].prompt.plain
|
||||
)
|
||||
assert posted_messages == []
|
||||
|
||||
def test_delete_request_shows_feedback_for_current_session(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions,
|
||||
latest_messages=sample_latest_messages,
|
||||
current_session_id="session-a",
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
|
||||
picker.action_request_delete()
|
||||
|
||||
assert_delete_state(picker, kind="feedback", option_id="local:session-a")
|
||||
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
|
||||
assert (
|
||||
"Can't delete current session"
|
||||
in option_list.replaced_prompts[-1].prompt.plain
|
||||
)
|
||||
assert posted_messages == []
|
||||
|
||||
picker.action_request_delete()
|
||||
|
||||
assert_delete_state(picker, kind="feedback", option_id="local:session-a")
|
||||
assert posted_messages == []
|
||||
|
||||
def test_pending_delete_blocks_resume_selection(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
|
||||
picker.action_request_delete()
|
||||
picker.action_request_delete()
|
||||
picker.on_option_list_option_selected(
|
||||
cast(OptionList.OptionSelected, FakeOptionEvent("local:session-a"))
|
||||
)
|
||||
picker.on_option_list_option_selected(
|
||||
cast(OptionList.OptionSelected, FakeOptionEvent("local:session-b"))
|
||||
)
|
||||
|
||||
assert len(posted_messages) == 1
|
||||
assert isinstance(posted_messages[0], SessionPickerApp.SessionDeleteRequested)
|
||||
assert_delete_state(picker, kind="pending", option_id="local:session-a")
|
||||
|
||||
def test_clear_pending_delete_restores_session_option(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
|
||||
picker.action_request_delete()
|
||||
picker.action_request_delete()
|
||||
|
||||
assert picker.clear_pending_delete("local:session-a") is True
|
||||
assert picker._delete_state is None
|
||||
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
|
||||
assert "Help me fix this bug" in option_list.replaced_prompts[-1].prompt.plain
|
||||
|
||||
def test_highlighting_another_session_clears_confirmation(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
picker.action_request_delete()
|
||||
|
||||
picker.on_option_list_option_highlighted(
|
||||
cast(OptionList.OptionHighlighted, FakeOptionEvent("local:session-b"))
|
||||
)
|
||||
|
||||
assert picker._delete_state is None
|
||||
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
|
||||
assert "Help me fix this bug" in option_list.replaced_prompts[-1].prompt.plain
|
||||
|
||||
def test_highlighting_another_session_clears_delete_feedback(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="remote:session-c")
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
picker.action_request_delete()
|
||||
|
||||
picker.on_option_list_option_highlighted(
|
||||
cast(OptionList.OptionHighlighted, FakeOptionEvent("local:session-b"))
|
||||
)
|
||||
|
||||
assert picker._delete_state is None
|
||||
assert option_list.replaced_prompts[-1].option_id == "remote:session-c"
|
||||
assert (
|
||||
"Add unit tests for the API"
|
||||
in option_list.replaced_prompts[-1].prompt.plain
|
||||
)
|
||||
|
||||
def test_escape_clears_confirmation_before_cancelling(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
picker.action_request_delete()
|
||||
|
||||
picker.action_cancel()
|
||||
|
||||
assert picker._delete_state is None
|
||||
assert posted_messages == []
|
||||
|
||||
picker.action_cancel()
|
||||
|
||||
assert len(posted_messages) == 1
|
||||
assert isinstance(posted_messages[0], SessionPickerApp.Cancelled)
|
||||
|
||||
def test_escape_clears_delete_feedback_before_cancelling(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="remote:session-c")
|
||||
posted_messages: list[object] = []
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
monkeypatch.setattr(picker, "post_message", posted_messages.append)
|
||||
picker.action_request_delete()
|
||||
|
||||
picker.action_cancel()
|
||||
|
||||
assert picker._delete_state is None
|
||||
assert posted_messages == []
|
||||
|
||||
picker.action_cancel()
|
||||
|
||||
assert len(posted_messages) == 1
|
||||
assert isinstance(posted_messages[0], SessionPickerApp.Cancelled)
|
||||
|
||||
def test_remove_session_updates_picker_state(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList(highlighted_option_id="local:session-a")
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
picker.action_request_delete()
|
||||
assert_delete_state(picker, kind="confirmation", option_id="local:session-a")
|
||||
|
||||
assert picker.remove_session("local:session-a") is True
|
||||
|
||||
assert [session.option_id for session in picker._sessions] == [
|
||||
"local:session-b",
|
||||
"remote:session-c",
|
||||
]
|
||||
assert "local:session-a" not in picker._latest_messages
|
||||
assert picker._delete_state is None
|
||||
assert option_list.removed_option_ids == ["local:session-a"]
|
||||
|
||||
def test_remove_missing_session_returns_false(
|
||||
self,
|
||||
sample_sessions: list[ResumeSessionInfo],
|
||||
sample_latest_messages: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
picker = SessionPickerApp(
|
||||
sessions=sample_sessions, latest_messages=sample_latest_messages
|
||||
)
|
||||
option_list = FakeOptionList()
|
||||
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
|
||||
|
||||
assert picker.remove_session("local:missing") is False
|
||||
|
||||
assert picker._sessions == sample_sessions
|
||||
assert picker._latest_messages == sample_latest_messages
|
||||
assert option_list.removed_option_ids == []
|
||||
|
||||
|
||||
class FakeOption:
|
||||
def __init__(self, option_id: str) -> None:
|
||||
self.id = option_id
|
||||
|
||||
|
||||
class FakeOptionEvent:
|
||||
def __init__(self, option_id: str) -> None:
|
||||
self.option = FakeOption(option_id)
|
||||
|
||||
|
||||
class ReplacedPrompt:
|
||||
def __init__(self, option_id: str, prompt: Text) -> None:
|
||||
self.option_id = option_id
|
||||
self.prompt = prompt
|
||||
|
||||
|
||||
class FakeOptionList:
|
||||
def __init__(self, highlighted_option_id: str | None = None) -> None:
|
||||
self.highlighted_option = (
|
||||
FakeOption(highlighted_option_id)
|
||||
if highlighted_option_id is not None
|
||||
else None
|
||||
)
|
||||
self.removed_option_ids: list[str] = []
|
||||
self.replaced_prompts: list[ReplacedPrompt] = []
|
||||
|
||||
def remove_option(self, option_id: str) -> None:
|
||||
self.removed_option_ids.append(option_id)
|
||||
|
||||
def replace_option_prompt(self, option_id: str, prompt: Text) -> None:
|
||||
self.replaced_prompts.append(ReplacedPrompt(option_id, prompt))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue