from __future__ import annotations from pathlib import Path import pytest from textual.pilot import Pilot from tests.conftest import build_test_vibe_config from tests.mock.utils import mock_llm_chunk from tests.snapshots.base_snapshot_test_app import BaseSnapshotTestApp, default_config from tests.snapshots.snap_compare import SnapCompare from tests.stubs.fake_backend import FakeBackend from vibe.cli.textual_ui.widgets.chat_input import paste_path from vibe.core.config import ModelConfig, ProviderConfig, VibeConfig from vibe.core.types import Backend PNG_BYTES = b"\x89PNG\r\n\x1a\n" + b"\x00" * 16 SNAP_ROOT = "/snap" def _vision_config() -> VibeConfig: base = default_config() models = [ ModelConfig( name="mistral-vibe-cli-latest", provider="mistral", alias="devstral-latest", supports_images=True, ) ] providers = [ ProviderConfig( name="mistral", api_base="https://api.mistral.ai/v1", api_key_env_var="MISTRAL_API_KEY", backend=Backend.MISTRAL, ) ] return build_test_vibe_config( disable_welcome_banner_animation=True, displayed_workdir=base.displayed_workdir, active_model="devstral-latest", models=models, providers=providers, ) @pytest.fixture(autouse=True) def _accept_snap_paths_as_images(monkeypatch: pytest.MonkeyPatch) -> None: def fake_is_image_file(candidate: str) -> bool: return candidate.startswith(f"{SNAP_ROOT}/") and candidate.endswith(( ".png", ".jpg", ".jpeg", ".gif", ".webp", )) monkeypatch.setattr(paste_path, "_is_image_file", fake_is_image_file) class _ImageAttachmentApp(BaseSnapshotTestApp): def __init__(self) -> None: super().__init__( config=_vision_config(), backend=FakeBackend(mock_llm_chunk(content="Got it.")), ) def test_snapshot_textbox_rewrites_typed_image_path(snap_compare: SnapCompare) -> None: async def run_before(pilot: Pilot) -> None: await pilot.press(*f"look {SNAP_ROOT}/shot.png") await pilot.pause(0.2) assert snap_compare( "test_ui_snapshot_image_attachments.py:_ImageAttachmentApp", terminal_size=(120, 36), run_before=run_before, ) def test_snapshot_user_message_with_image_footer_singular( snap_compare: SnapCompare, tmp_working_directory: Path ) -> None: (tmp_working_directory / "shot.png").write_bytes(PNG_BYTES) async def run_before(pilot: Pilot) -> None: # Trailing space dismisses the @-mention autocomplete popup so that # `enter` submits the message instead of accepting a suggestion. await pilot.press(*"look @shot.png ") await pilot.press("enter") await pilot.pause(0.4) assert snap_compare( "test_ui_snapshot_image_attachments.py:_ImageAttachmentApp", terminal_size=(120, 36), run_before=run_before, ) def test_snapshot_user_message_with_image_footer_plural( snap_compare: SnapCompare, tmp_working_directory: Path ) -> None: (tmp_working_directory / "a.png").write_bytes(PNG_BYTES) (tmp_working_directory / "b.png").write_bytes(PNG_BYTES) async def run_before(pilot: Pilot) -> None: await pilot.press(*"compare @a.png @b.png ") await pilot.press("enter") await pilot.pause(0.4) assert snap_compare( "test_ui_snapshot_image_attachments.py:_ImageAttachmentApp", terminal_size=(120, 36), run_before=run_before, )