Initial commit

Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai>
Co-Authored-By: Laure Hugo <laure.hugo@mistral.ai>
Co-Authored-By: Benjamin Trom <benjamin.trom@mistral.ai>
Co-Authored-By: Mathias Gesbert <mathias.gesbert@ext.mistral.ai>
Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai>
Co-Authored-By: Clément Drouin <clement.drouin@mistral.ai>
Co-Authored-By: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-Authored-By: Valentin Berard <val@mistral.ai>
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Quentin Torroba 2025-12-09 13:13:22 +01:00 committed by Quentin Torroba
commit fa15fc977b
200 changed files with 30484 additions and 0 deletions

View file

@ -0,0 +1,69 @@
from __future__ import annotations
from pathlib import Path
import sys
from typing import override
import pytest
from textual.app import App
from vibe.setup import onboarding
class StubApp(App[str | None]):
def __init__(self, return_value: str | None) -> None:
super().__init__()
self._return_value = return_value
@override
def run(self, *args: object, **kwargs: object) -> str | None:
return self._return_value
def _patch_env_file(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
env_file = tmp_path / ".env"
monkeypatch.setattr(onboarding, "GLOBAL_ENV_FILE", env_file, raising=False)
return env_file
def _exit_raiser(code: int = 0) -> None:
raise SystemExit(code)
def test_exits_on_cancel(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
_patch_env_file(monkeypatch, tmp_path)
monkeypatch.setattr(sys, "exit", _exit_raiser)
with pytest.raises(SystemExit) as excinfo:
onboarding.run_onboarding(StubApp(None))
assert excinfo.value.code == 0
out = capsys.readouterr().out
assert "Setup cancelled. See you next time!" in out
def test_warns_on_save_error(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
_patch_env_file(monkeypatch, tmp_path)
monkeypatch.setattr(sys, "exit", _exit_raiser)
onboarding.run_onboarding(StubApp("save_error:disk full"))
out = capsys.readouterr().out
assert "Could not save API key" in out
assert "disk full" in out
def test_successfully_completes(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
_patch_env_file(monkeypatch, tmp_path)
monkeypatch.setattr(sys, "exit", _exit_raiser)
onboarding.run_onboarding(StubApp("completed"))
out = capsys.readouterr().out
assert out == ""

View file

@ -0,0 +1,124 @@
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
from typing import Any
import pytest
from textual.events import Resize
from textual.geometry import Size
from textual.pilot import Pilot
from textual.widgets import Input
from vibe.core import config as core_config
from vibe.setup.onboarding import OnboardingApp
import vibe.setup.onboarding.screens.api_key as api_key_module
from vibe.setup.onboarding.screens.api_key import ApiKeyScreen
from vibe.setup.onboarding.screens.theme_selection import THEMES, ThemeSelectionScreen
async def _wait_for(
condition: Callable[[], bool],
pilot: Pilot,
timeout: float = 5.0,
interval: float = 0.05,
) -> None:
elapsed = 0.0
while not condition():
await pilot.pause(interval)
if (elapsed := elapsed + interval) >= timeout:
msg = "Timed out waiting for condition."
raise AssertionError(msg)
@pytest.fixture()
def onboarding_app(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> tuple[OnboardingApp, Path, dict[str, Any]]:
vibe_home = tmp_path / ".vibe"
env_file = vibe_home / ".env"
saved_updates: dict[str, Any] = {}
def record_updates(updates: dict[str, Any]) -> None:
saved_updates.update(updates)
monkeypatch.setenv("VIBE_HOME", str(vibe_home))
for module in (core_config, api_key_module):
monkeypatch.setattr(module, "GLOBAL_CONFIG_DIR", vibe_home, raising=False)
monkeypatch.setattr(module, "GLOBAL_ENV_FILE", env_file, raising=False)
monkeypatch.setattr(
core_config.VibeConfig,
"save_updates",
classmethod(lambda cls, updates: record_updates(updates)),
)
return OnboardingApp(), env_file, saved_updates
async def pass_welcome_screen(pilot: Pilot) -> None:
welcome_screen = pilot.app.get_screen("welcome")
await _wait_for(
lambda: not welcome_screen.query_one("#enter-hint").has_class("hidden"), pilot
)
await pilot.press("enter")
await _wait_for(lambda: isinstance(pilot.app.screen, ThemeSelectionScreen), pilot)
@pytest.mark.asyncio
async def test_ui_gets_through_the_onboarding_successfully(
onboarding_app: tuple[OnboardingApp, Path, dict[str, Any]],
) -> None:
app, env_file, config_updates = onboarding_app
api_key_value = "sk-onboarding-test-key"
async with app.run_test() as pilot:
await pass_welcome_screen(pilot)
await pilot.press("enter")
await _wait_for(lambda: isinstance(app.screen, ApiKeyScreen), pilot)
api_screen = app.screen
input_widget = api_screen.query_one("#key", Input)
await pilot.press(*api_key_value)
assert input_widget.value == api_key_value
await pilot.press("enter")
await _wait_for(lambda: app.return_value is not None, pilot, timeout=2.0)
assert app.return_value == "completed"
assert env_file.is_file()
env_contents = env_file.read_text(encoding="utf-8")
assert "MISTRAL_API_KEY" in env_contents
assert api_key_value in env_contents
assert config_updates.get("textual_theme") == app.theme
@pytest.mark.asyncio
async def test_ui_can_pick_a_theme_and_saves_selection(
onboarding_app: tuple[OnboardingApp, Path, dict[str, Any]],
) -> None:
app, _, config_updates = onboarding_app
async with app.run_test() as pilot:
await pass_welcome_screen(pilot)
theme_screen = app.screen
app.post_message(
Resize(Size(40, 10), Size(40, 10))
) # trigger the resize event handler
preview = theme_screen.query_one("#preview")
assert preview.styles.max_height is not None
target_theme = "gruvbox"
assert target_theme in THEMES
start_index = THEMES.index(app.theme)
target_index = THEMES.index(target_theme)
steps_down = (target_index - start_index) % len(THEMES)
await pilot.press(*["down"] * steps_down)
assert app.theme == target_theme
await pilot.press("enter")
await _wait_for(lambda: isinstance(app.screen, ApiKeyScreen), pilot)
assert config_updates.get("textual_theme") == target_theme