Files
rich-print/tests/test_cli.py

280 lines
9.0 KiB
Python

"""Tests for the CLI entry point."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
from typer.testing import CliRunner
from rp import __version__
from rp.cli import app, main
runner = CliRunner()
class TestVersionFlag:
"""Tests for the version flags."""
def test_version_long_flag(self) -> None:
result = runner.invoke(app, ["--version"])
assert result.exit_code == 0
assert __version__ in result.output
def test_version_short_flag(self) -> None:
result = runner.invoke(app, ["-v"])
assert result.exit_code == 0
assert __version__ in result.output
def test_version_output_format(self) -> None:
result = runner.invoke(app, ["--version"])
assert result.exit_code == 0
assert "Rich Print (rp)" in result.output
assert "License: MIT" in result.output
class TestFileInput:
"""Tests for reading from a file argument."""
def test_render_python_file(self, tmp_file) -> None:
file = tmp_file("def hello(): pass\n", suffix=".py")
result = runner.invoke(app, [str(file), "--no-pager"])
assert result.exit_code == 0
assert result.output
def test_render_markdown_file(self, tmp_file) -> None:
file = tmp_file("# Hello\n\nWorld\n", suffix=".md")
result = runner.invoke(app, [str(file), "--no-pager"])
assert result.exit_code == 0
def test_render_json_file(self, tmp_file, sample_json: str) -> None:
file = tmp_file(sample_json, suffix=".json")
result = runner.invoke(app, [str(file), "--no-pager"])
assert result.exit_code == 0
def test_detect_type_called_with_file_path(self, tmp_file) -> None:
"""detect_type receives the file path string."""
file = tmp_file("x = 1\n", suffix=".py")
with patch("rp.cli.detect_type") as mock_detect_type:
mock_detect_type.return_value = "python"
with patch("rp.cli.render"):
runner.invoke(app, [str(file), "--no-pager"])
mock_detect_type.assert_called_once_with(
path=str(file),
content="x = 1\n",
explicit_type=None,
guess_content=False,
)
def test_render_called_with_correct_options(self, tmp_file) -> None:
"""Render receives the right theme, line_numbers, pager flags."""
file = tmp_file("x = 1\n", suffix=".py")
with patch("rp.cli.detect_type", return_value="python"):
with patch("rp.cli.render") as mock_render:
runner.invoke(
app,
[
str(file),
"--no-pager",
"--theme",
"github-dark",
"--line-numbers",
],
)
call_args = mock_render.call_args
assert call_args is not None
assert call_args[0][0] == "x = 1\n"
assert call_args[0][1] == "python"
options = call_args[0][3]
assert options.theme == "github-dark"
assert options.line_numbers is True
assert options.pager is False
def test_file_not_found(self) -> None:
result = runner.invoke(app, ["/nonexistent/file.py"])
assert result.exit_code == 1
assert "Error" in result.output
def test_directory_error(self, tmp_path: Path) -> None:
result = runner.invoke(app, [str(tmp_path)])
assert result.exit_code == 1
assert "directory" in result.output.lower() or "Error" in result.output
def test_empty_file(self, tmp_file) -> None:
file = tmp_file("", suffix=".txt")
result = runner.invoke(app, [str(file)])
assert result.exit_code == 0
assert "Empty" in result.output or "empty" in result.output.lower()
def test_binary_file_error(self, tmp_file) -> None:
file = tmp_file("placeholder", suffix=".bin")
with patch.object(
Path,
"read_text",
side_effect=UnicodeDecodeError("utf-8", b"x", 0, 1, "boom"),
):
result = runner.invoke(app, [str(file)])
assert result.exit_code == 1
assert "Cannot read binary file" in result.output
def test_os_error_when_reading_file(self, tmp_file) -> None:
file = tmp_file("placeholder", suffix=".txt")
with patch.object(Path, "read_text", side_effect=OSError("read failed")):
result = runner.invoke(app, [str(file)])
assert result.exit_code == 1
assert "read failed" in result.output
class TestStdinInput:
"""Tests for reading from stdin."""
def test_stdin_python(self) -> None:
result = runner.invoke(app, ["--no-pager"], input="x = 1\n")
assert result.exit_code == 0
def test_stdin_json(self, sample_json: str) -> None:
result = runner.invoke(app, ["--no-pager"], input=sample_json)
assert result.exit_code == 0
def test_stdin_empty(self) -> None:
result = runner.invoke(app, [], input="")
assert result.exit_code == 0
assert "Empty" in result.output or "empty" in result.output.lower()
def test_stdin_with_dash(self) -> None:
result = runner.invoke(app, ["-", "--no-pager"], input="hello\n")
assert result.exit_code == 0
def test_stdin_passes_none_path(self) -> None:
"""When reading from stdin, detect_type should receive path=None."""
with patch("rp.cli.detect_type") as mock_detect_type:
mock_detect_type.return_value = "text"
with patch("rp.cli.render"):
runner.invoke(app, ["--no-pager"], input="hello\n")
mock_detect_type.assert_called_once_with(
path=None,
content="hello\n",
explicit_type=None,
guess_content=False,
)
def test_stdin_shows_tty_prompt(self) -> None:
"""When stdin is a TTY and no input is piped, show the reading hint."""
with patch("rp.cli.sys.stdin.isatty", return_value=True):
with patch("rp.cli.sys.stdin.read", return_value="hello\n"):
with patch("rp.cli.render"):
with patch("rp.cli.Console.print") as mock_print:
main(pager=False)
mock_print.assert_any_call(
"[dim]Reading from stdin. Press Ctrl+D to end.[/dim]"
)
class TestTypeOverride:
"""Tests for the type override flags."""
def test_explicit_type(self, tmp_file) -> None:
file = tmp_file('{"key": "value"}\n', suffix=".txt")
result = runner.invoke(app, [str(file), "--type", "json", "--no-pager"])
assert result.exit_code == 0
def test_explicit_type_is_forwarded(self, tmp_file) -> None:
"""The --type flag value is passed as explicit_type to detect_type."""
file = tmp_file('{"key": "value"}\n', suffix=".txt")
with patch("rp.cli.detect_type") as mock_detect_type:
mock_detect_type.return_value = "json"
with patch("rp.cli.render"):
runner.invoke(app, [str(file), "--type", "json", "--no-pager"])
_, kwargs = mock_detect_type.call_args
assert kwargs["explicit_type"] == "json"
assert kwargs["guess_content"] is False
def test_explicit_type_short_flag(self, tmp_file) -> None:
file = tmp_file("def foo(): pass\n", suffix=".txt")
result = runner.invoke(app, [str(file), "-t", "python", "--no-pager"])
assert result.exit_code == 0
class TestThemeOption:
"""Tests for the theme option."""
def test_custom_theme(self, tmp_file) -> None:
file = tmp_file("x = 1\n", suffix=".py")
result = runner.invoke(app, [str(file), "--theme", "github-dark", "--no-pager"])
assert result.exit_code == 0
class TestGuessContentOption:
"""Tests for the guess-content flag."""
def test_guess_content_is_forwarded(self) -> None:
with patch("rp.cli.detect_type") as mock_detect_type:
mock_detect_type.return_value = "diff"
with patch("rp.cli.render"):
runner.invoke(app, ["--guess-content", "--no-pager"], input="hello\n")
_, kwargs = mock_detect_type.call_args
assert kwargs["guess_content"] is True
def test_guess_content_disabled_by_default(self) -> None:
with patch("rp.cli.detect_type") as mock_detect_type:
mock_detect_type.return_value = "text"
with patch("rp.cli.render"):
runner.invoke(app, ["--no-pager"], input="hello\n")
_, kwargs = mock_detect_type.call_args
assert kwargs["guess_content"] is False
class TestLineNumbersOption:
"""Tests for the line number flags."""
def test_line_numbers_enabled(self, tmp_file) -> None:
file = tmp_file("x = 1\n", suffix=".py")
result = runner.invoke(app, [str(file), "--line-numbers", "--no-pager"])
assert result.exit_code == 0
def test_line_numbers_disabled(self, tmp_file) -> None:
file = tmp_file("x = 1\n", suffix=".py")
result = runner.invoke(app, [str(file), "--no-line-numbers", "--no-pager"])
assert result.exit_code == 0