Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Laure Hugo <201583486+laure0303@users.noreply.github.com>
Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com>
Co-authored-by: Peter Evers <peter.evers@mistral.ai>
Co-authored-by: Jules YZERD <newtonlormont@gmail.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-06-30 15:03:21 +02:00 committed by GitHub
parent d50704e694
commit 4e495f658d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 1042 additions and 552 deletions

View file

@ -4,9 +4,11 @@ from typing import Any, cast
from unittest.mock import MagicMock, patch
import pytest
from textual.content import Content
from textual.widgets import OptionList
from tests.stubs.fake_connector_registry import FakeConnectorRegistry
from vibe.cli.textual_ui.shortcut_hints import SHORTCUT_STYLE
from vibe.cli.textual_ui.widgets.connector_auth_app import (
ConnectorAuthApp,
_AuthOptionId,
@ -167,13 +169,15 @@ class TestAuthActions:
app._toggle_url()
assert app._auth_url_visible is True
text = detail.update.call_args.args[0]
assert "https://auth.example.com/oauth" in text
assert "Once authenticated" in text
assert isinstance(text, Content)
assert "https://auth.example.com/oauth" in text.plain
assert "Once authenticated" in text.plain
app._toggle_url()
assert app._auth_url_visible is False
text = detail.update.call_args.args[0]
assert "https://auth.example.com/oauth" not in text
assert isinstance(text, Content)
assert "https://auth.example.com/oauth" not in text.plain
def test_toggle_url_noop_without_url(self) -> None:
app = _make_app()
@ -193,8 +197,10 @@ class TestDetailText:
app._update_detail_text()
text = detail.update.call_args.args[0]
assert "Once authenticated, press R to refresh" in text
assert "https://auth.example.com" not in text
assert isinstance(text, Content)
assert "Once authenticated, press r to refresh" in text.plain
assert "https://auth.example.com" not in text.plain
assert any(span.style == SHORTCUT_STYLE for span in text.spans)
def test_with_url_visible(self) -> None:
app = cast(Any, _make_app())
@ -206,11 +212,13 @@ class TestDetailText:
app._update_detail_text()
text = detail.update.call_args.args[0]
assert "https://auth.example.com/oauth" in text
assert "Once authenticated, press R to refresh" in text
assert isinstance(text, Content)
assert "https://auth.example.com/oauth" in text.plain
assert "Once authenticated, press r to refresh" in text.plain
assert any(span.style == SHORTCUT_STYLE for span in text.spans)
# URL should come before the footer
url_pos = text.index("https://auth.example.com/oauth")
footer_pos = text.index("Once authenticated")
url_pos = text.plain.index("https://auth.example.com/oauth")
footer_pos = text.plain.index("Once authenticated")
assert url_pos < footer_pos

View file

@ -5,10 +5,12 @@ from typing import Any
from unittest.mock import AsyncMock, MagicMock
import pytest
from textual.content import Content
from textual.widgets.option_list import Option
from tests.stubs.fake_connector_registry import FakeConnectorRegistry
from tests.stubs.fake_mcp_registry import FakeMCPRegistry
from vibe.cli.textual_ui.shortcut_hints import SHORTCUT_STYLE
from vibe.cli.textual_ui.widgets.mcp_app import (
_LIST_VIEW_HELP_AUTH,
_LIST_VIEW_HELP_TOOLS,
@ -431,9 +433,10 @@ class TestConnectorAuthRequested:
app.post_message.assert_not_called()
option_list.add_option.assert_called_once()
assert "press R to refresh" in str(
option_list.add_option.call_args.args[0].prompt
)
prompt = option_list.add_option.call_args.args[0].prompt
assert isinstance(prompt, Content)
assert "press r to refresh" in prompt.plain
assert any(span.style == SHORTCUT_STYLE for span in prompt.spans)
def test_connected_connector_with_no_indexed_tools_shows_message(self) -> None:
registry = MagicMock()

View file

@ -72,6 +72,31 @@ class TestQuestionAppState:
assert len(app.answers) == 0
assert len(app.other_texts) == 0
def test_more_than_four_options_accepted_and_rendered(self):
from vibe.cli.textual_ui.widgets.question_app import QuestionApp
args = AskUserQuestionArgs(
questions=[
Question(
question="Pick an analysis type",
header="Analysis",
options=[
Choice(label="Financial"),
Choice(label="Operational"),
Choice(label="Strategic"),
Choice(label="Competitive"),
Choice(label="Multi-source"),
],
)
]
)
app = QuestionApp(args)
assert app.max_options == 5
# 5 options + Other = 6 (no Submit for single-select)
assert app._total_options == 6
def test_total_options_single_select(self, single_question_args):
from vibe.cli.textual_ui.widgets.question_app import QuestionApp

View file

@ -42,7 +42,7 @@ async def _wait_for_error_message_containing(
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
for error in vibe_app.query(ErrorMessage):
if text in error._error:
if text in str(error._error):
return error
await pilot.pause(0.05)
raise TimeoutError(

View file

@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from textual.app import WINDOWS
from tests.conftest import build_test_vibe_app
from tests.conftest import build_test_vibe_app, build_test_vibe_config
from vibe.cli.textual_ui.app import VibeApp, _run_app_with_cleanup
from vibe.cli.textual_ui.quit_manager import QUIT_CONFIRM_DELAY, QuitManager
@ -187,6 +187,19 @@ class TestActionDeleteRightOrQuit:
"Ctrl+D", "1 queued message will be discarded"
)
def test_quits_immediately_when_confirmation_disabled(self) -> None:
app = build_test_vibe_app(
config=build_test_vibe_config(ask_confirmation_on_exit=False)
)
with (
patch.object(app, "_get_chat_input", return_value=None),
patch.object(app, "_force_quit") as mock_quit,
patch.object(app._quit_manager, "request_confirmation") as mock_confirm,
):
app.action_delete_right_or_quit()
mock_quit.assert_called_once()
mock_confirm.assert_not_called()
@pytest.mark.asyncio
async def test_shutdown_cleanup_cancels_in_flight_tasks(app: VibeApp) -> None:

View file

@ -7,6 +7,7 @@ import pytest
from rich.text import Text
from textual.widgets import OptionList
from vibe.cli.textual_ui.shortcut_hints import SHORTCUT_STYLE
from vibe.cli.textual_ui.widgets.session_picker import (
SessionPickerApp,
_format_relative_time,
@ -170,7 +171,7 @@ class TestSessionPickerAppBindings:
def test_has_delete_binding(self) -> None:
assert "d" in self._get_binding_keys()
assert "D" in self._get_binding_keys()
assert "D" not in self._get_binding_keys()
class TestSessionPickerSessionRemoval:
@ -192,9 +193,9 @@ class TestSessionPickerSessionRemoval:
assert_delete_state(picker, kind="confirmation", option_id="session-a")
assert option_list.replaced_prompts[-1].option_id == "session-a"
assert (
"Press D again to delete" in option_list.replaced_prompts[-1].prompt.plain
)
prompt = option_list.replaced_prompts[-1].prompt
assert "Press d again to delete" in prompt.plain
assert any(span.style == SHORTCUT_STYLE for span in prompt.spans)
assert posted_messages == []
def test_second_delete_request_posts_delete_message(