- Replaces fragile input() hacks with robust prompt_toolkit.PromptSession - Native support for multiline paste, history, and clean display - Restores animated spinner in _thinking_ctx (now safe) - Replaces boxed Panel with clean header for easier copying - Adds prompt-toolkit dependency - Adds new unit tests for input layer
59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
from prompt_toolkit.formatted_text import HTML
|
|
|
|
from nanobot.cli import commands
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_prompt_session():
|
|
"""Mock the global prompt session."""
|
|
mock_session = MagicMock()
|
|
mock_session.prompt_async = AsyncMock()
|
|
with patch("nanobot.cli.commands._PROMPT_SESSION", mock_session):
|
|
yield mock_session
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_interactive_input_async_returns_input(mock_prompt_session):
|
|
"""Test that _read_interactive_input_async returns the user input from prompt_session."""
|
|
mock_prompt_session.prompt_async.return_value = "hello world"
|
|
|
|
result = await commands._read_interactive_input_async()
|
|
|
|
assert result == "hello world"
|
|
mock_prompt_session.prompt_async.assert_called_once()
|
|
args, _ = mock_prompt_session.prompt_async.call_args
|
|
assert isinstance(args[0], HTML) # Verify HTML prompt is used
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_interactive_input_async_handles_eof(mock_prompt_session):
|
|
"""Test that EOFError converts to KeyboardInterrupt."""
|
|
mock_prompt_session.prompt_async.side_effect = EOFError()
|
|
|
|
with pytest.raises(KeyboardInterrupt):
|
|
await commands._read_interactive_input_async()
|
|
|
|
|
|
def test_init_prompt_session_creates_session():
|
|
"""Test that _init_prompt_session initializes the global session."""
|
|
# Ensure global is None before test
|
|
commands._PROMPT_SESSION = None
|
|
|
|
with patch("nanobot.cli.commands.PromptSession") as MockSession, \
|
|
patch("nanobot.cli.commands.FileHistory") as MockHistory, \
|
|
patch("pathlib.Path.home") as mock_home:
|
|
|
|
mock_home.return_value = MagicMock()
|
|
|
|
commands._init_prompt_session()
|
|
|
|
assert commands._PROMPT_SESSION is not None
|
|
MockSession.assert_called_once()
|
|
_, kwargs = MockSession.call_args
|
|
assert kwargs["multiline"] is False
|
|
assert kwargs["enable_open_in_editor"] is False
|