Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Hdandria <henri.dandria@mistral.ai> Co-authored-by: Ivana Dunisijevic <ivana.dunisijevic@mistral.ai> Co-authored-by: Jean Burellier <sheplu@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Mert Unsal <mert.unsal@mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@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: Val <102326092+vdeva@users.noreply.github.com> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: renovate-mistral[bot] <253709520+renovate-mistral[bot]@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
211 lines
6.8 KiB
Python
211 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import build_test_vibe_app
|
|
from vibe.cli.textual_ui.app import VibeApp
|
|
from vibe.cli.textual_ui.widgets.chat_input.container import ChatInputContainer
|
|
from vibe.cli.textual_ui.widgets.messages import (
|
|
BashOutputMessage,
|
|
QueueHeaderMessage,
|
|
WarningMessage,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def vibe_app() -> VibeApp:
|
|
return build_test_vibe_app()
|
|
|
|
|
|
async def _wait_until(pilot, predicate, timeout: float = 2.0) -> bool:
|
|
deadline = time.monotonic() + timeout
|
|
while time.monotonic() < deadline:
|
|
if predicate():
|
|
return True
|
|
await pilot.pause(0.05)
|
|
return False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_no_queue_header_when_empty(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test():
|
|
headers = list(vibe_app.query(QueueHeaderMessage))
|
|
assert headers == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_bash_submitted_during_running_bash_is_queued(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test() as pilot:
|
|
chat_input = vibe_app.query_one(ChatInputContainer)
|
|
chat_input.value = "!sleep 1"
|
|
await pilot.press("enter")
|
|
|
|
assert await _wait_until(
|
|
pilot, lambda: vibe_app._bash_task is not None, timeout=2.0
|
|
)
|
|
|
|
chat_input.value = "!echo queued"
|
|
await pilot.press("enter")
|
|
|
|
assert len(vibe_app._input_queue) == 1
|
|
assert vibe_app._input_queue.items[0].content == "echo queued"
|
|
|
|
headers = list(vibe_app.query(QueueHeaderMessage))
|
|
assert len(headers) == 1
|
|
|
|
queued_bashes = [w for w in vibe_app.query(BashOutputMessage) if w._queued]
|
|
assert len(queued_bashes) == 1
|
|
|
|
await pilot.press("ctrl+c")
|
|
assert await _wait_until(
|
|
pilot, lambda: len(vibe_app._input_queue) == 0, timeout=2.0
|
|
)
|
|
await pilot.press("escape")
|
|
assert await _wait_until(
|
|
pilot, lambda: vibe_app._bash_task is None, timeout=5.0
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_slash_command_rejected_with_warning_when_busy(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test() as pilot:
|
|
chat_input = vibe_app.query_one(ChatInputContainer)
|
|
chat_input.value = "!sleep 0.3"
|
|
await pilot.press("enter")
|
|
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=1.0)
|
|
|
|
chat_input.value = "/help"
|
|
await pilot.press("enter")
|
|
|
|
assert not list(vibe_app.query(WarningMessage))
|
|
assert any(
|
|
"Slash commands cannot be queued" in notification.message
|
|
for notification in vibe_app._notifications
|
|
)
|
|
assert len(vibe_app._input_queue) == 0
|
|
assert chat_input.value.startswith("/help")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ctrl_c_pops_last_queued_item_lifo(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test() as pilot:
|
|
chat_input = vibe_app.query_one(ChatInputContainer)
|
|
chat_input.value = "!sleep 2"
|
|
await pilot.press("enter")
|
|
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=2.0)
|
|
|
|
chat_input.value = "!echo first"
|
|
await pilot.press("enter")
|
|
chat_input.value = "!echo second"
|
|
await pilot.press("enter")
|
|
|
|
assert len(vibe_app._input_queue) == 2
|
|
|
|
await pilot.press("ctrl+c")
|
|
assert len(vibe_app._input_queue) == 1
|
|
assert vibe_app._input_queue.items[0].content == "echo first"
|
|
|
|
await pilot.press("escape")
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is None, timeout=5.0)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_escape_pauses_queue_when_job_running(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test() as pilot:
|
|
chat_input = vibe_app.query_one(ChatInputContainer)
|
|
chat_input.value = "!sleep 2"
|
|
await pilot.press("enter")
|
|
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=2.0)
|
|
|
|
chat_input.value = "!echo queued"
|
|
await pilot.press("enter")
|
|
assert len(vibe_app._input_queue) == 1
|
|
|
|
await pilot.press("escape")
|
|
assert vibe_app._input_queue.paused
|
|
assert len(vibe_app._input_queue) == 1
|
|
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is None, timeout=5.0)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_drain_runs_queued_bashes_in_fifo_order(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test() as pilot:
|
|
chat_input = vibe_app.query_one(ChatInputContainer)
|
|
chat_input.value = "!sleep 0.2"
|
|
await pilot.press("enter")
|
|
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=1.0)
|
|
|
|
chat_input.value = "!echo first"
|
|
await pilot.press("enter")
|
|
chat_input.value = "!echo second"
|
|
await pilot.press("enter")
|
|
|
|
await _wait_until(
|
|
pilot,
|
|
lambda: (
|
|
len(list(vibe_app.query(BashOutputMessage))) == 3
|
|
and all(not m._pending for m in vibe_app.query(BashOutputMessage))
|
|
),
|
|
timeout=5.0,
|
|
)
|
|
|
|
msgs = list(vibe_app.query(BashOutputMessage))
|
|
assert len(msgs) == 3
|
|
assert vibe_app._input_queue.paused is False
|
|
assert len(vibe_app._input_queue) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_on_empty_input_flushes_paused_queue(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test() as pilot:
|
|
chat_input = vibe_app.query_one(ChatInputContainer)
|
|
chat_input.value = "!sleep 2"
|
|
await pilot.press("enter")
|
|
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=2.0)
|
|
|
|
chat_input.value = "!echo queued"
|
|
await pilot.press("enter")
|
|
assert len(vibe_app._input_queue) == 1
|
|
|
|
await pilot.press("escape")
|
|
assert vibe_app._input_queue.paused
|
|
|
|
await _wait_until(pilot, lambda: vibe_app._bash_task is None, timeout=10.0)
|
|
|
|
chat_input.value = ""
|
|
await pilot.press("enter")
|
|
|
|
await _wait_until(
|
|
pilot,
|
|
lambda: (
|
|
not vibe_app._input_queue.paused and len(vibe_app._input_queue) == 0
|
|
),
|
|
timeout=10.0,
|
|
)
|
|
|
|
assert not vibe_app._input_queue.paused
|
|
assert len(vibe_app._input_queue) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_quit_warning_shows_queue_count(vibe_app: VibeApp) -> None:
|
|
async with vibe_app.run_test():
|
|
vibe_app._input_queue.append_prompt("a")
|
|
vibe_app._input_queue.append_prompt("b")
|
|
warning = vibe_app._queue.quit_warning_extra()
|
|
assert warning == "2 queued messages will be discarded"
|
|
|
|
vibe_app._input_queue.pop_last()
|
|
warning = vibe_app._queue.quit_warning_extra()
|
|
assert warning == "1 queued message will be discarded"
|
|
|
|
vibe_app._input_queue.pop_last()
|
|
assert vibe_app._queue.quit_warning_extra() == ""
|