summaryrefslogtreecommitdiffstats
path: root/tests/test_console.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/test_console.py545
1 files changed, 545 insertions, 0 deletions
diff --git a/tests/test_console.py b/tests/test_console.py
new file mode 100644
index 0000000..ad1e0f1
--- /dev/null
+++ b/tests/test_console.py
@@ -0,0 +1,545 @@
+import datetime
+import io
+import os
+import sys
+import tempfile
+from typing import Optional
+
+import pytest
+
+from rich import errors
+from rich.color import ColorSystem
+from rich.console import (
+ CaptureError,
+ Console,
+ ConsoleDimensions,
+ ConsoleOptions,
+ render_group,
+)
+from rich.measure import measure_renderables
+from rich.pager import SystemPager
+from rich.panel import Panel
+from rich.status import Status
+from rich.style import Style
+from rich.text import Text
+
+
+def test_dumb_terminal():
+ console = Console(force_terminal=True)
+ assert console.color_system is not None
+
+ console = Console(force_terminal=True, _environ={"TERM": "dumb"})
+ assert console.color_system is None
+ width, height = console.size
+ assert width == 80
+ assert height == 25
+
+
+def test_soft_wrap():
+ console = Console(file=io.StringIO(), width=20, soft_wrap=True)
+ console.print("foo " * 10)
+ assert console.file.getvalue() == "foo " * 20
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
+def test_16color_terminal():
+ console = Console(
+ force_terminal=True, _environ={"TERM": "xterm-16color"}, legacy_windows=False
+ )
+ assert console.color_system == "standard"
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
+def test_truecolor_terminal():
+ console = Console(
+ force_terminal=True,
+ legacy_windows=False,
+ _environ={"COLORTERM": "truecolor", "TERM": "xterm-16color"},
+ )
+ assert console.color_system == "truecolor"
+
+
+def test_console_options_update():
+ options = ConsoleOptions(
+ ConsoleDimensions(80, 25),
+ legacy_windows=False,
+ min_width=10,
+ max_width=20,
+ is_terminal=False,
+ encoding="utf-8",
+ )
+ options1 = options.update(width=15)
+ assert options1.min_width == 15 and options1.max_width == 15
+
+ options2 = options.update(min_width=5, max_width=15, justify="right")
+ assert (
+ options2.min_width == 5
+ and options2.max_width == 15
+ and options2.justify == "right"
+ )
+
+ options_copy = options.update()
+ assert options_copy == options and options_copy is not options
+
+
+def test_init():
+ console = Console(color_system=None)
+ assert console._color_system == None
+ console = Console(color_system="standard")
+ assert console._color_system == ColorSystem.STANDARD
+ console = Console(color_system="auto")
+
+
+def test_size():
+ console = Console()
+ w, h = console.size
+ assert console.width == w
+
+ console = Console(width=99, height=101)
+ w, h = console.size
+ assert w == 99 and h == 101
+
+
+def test_repr():
+ console = Console()
+ assert isinstance(repr(console), str)
+ assert isinstance(str(console), str)
+
+
+def test_print():
+ console = Console(file=io.StringIO(), color_system="truecolor")
+ console.print("foo")
+ assert console.file.getvalue() == "foo\n"
+
+
+def test_log():
+ console = Console(
+ file=io.StringIO(),
+ width=80,
+ color_system="truecolor",
+ log_time_format="TIME",
+ log_path=False,
+ )
+ console.log("foo", style="red")
+ expected = "\x1b[2;36mTIME\x1b[0m\x1b[2;36m \x1b[0m\x1b[31mfoo\x1b[0m\x1b[31m \x1b[0m\n"
+ result = console.file.getvalue()
+ print(repr(result))
+ assert result == expected
+
+
+def test_log_milliseconds():
+ def time_formatter(timestamp: datetime) -> Text:
+ return Text("TIME")
+
+ console = Console(
+ file=io.StringIO(), width=40, log_time_format=time_formatter, log_path=False
+ )
+ console.log("foo")
+ result = console.file.getvalue()
+ assert result == "TIME foo \n"
+
+
+def test_print_empty():
+ console = Console(file=io.StringIO(), color_system="truecolor")
+ console.print()
+ assert console.file.getvalue() == "\n"
+
+
+def test_markup_highlight():
+ console = Console(file=io.StringIO(), color_system="truecolor")
+ console.print("'[bold]foo[/bold]'")
+ assert (
+ console.file.getvalue()
+ == "\x1b[32m'\x1b[0m\x1b[1;32mfoo\x1b[0m\x1b[32m'\x1b[0m\n"
+ )
+
+
+def test_print_style():
+ console = Console(file=io.StringIO(), color_system="truecolor")
+ console.print("foo", style="bold")
+ assert console.file.getvalue() == "\x1b[1mfoo\x1b[0m\n"
+
+
+def test_show_cursor():
+ console = Console(file=io.StringIO(), force_terminal=True, legacy_windows=False)
+ console.show_cursor(False)
+ console.print("foo")
+ console.show_cursor(True)
+ assert console.file.getvalue() == "\x1b[?25lfoo\n\x1b[?25h"
+
+
+def test_clear():
+ console = Console(file=io.StringIO(), force_terminal=True)
+ console.clear()
+ console.clear(home=False)
+ assert console.file.getvalue() == "\033[2J\033[H" + "\033[2J"
+
+
+def test_clear_no_terminal():
+ console = Console(file=io.StringIO())
+ console.clear()
+ console.clear(home=False)
+ assert console.file.getvalue() == ""
+
+
+def test_get_style():
+ console = Console()
+ console.get_style("repr.brace") == Style(bold=True)
+
+
+def test_get_style_default():
+ console = Console()
+ console.get_style("foobar", default="red") == Style(color="red")
+
+
+def test_get_style_error():
+ console = Console()
+ with pytest.raises(errors.MissingStyle):
+ console.get_style("nosuchstyle")
+ with pytest.raises(errors.MissingStyle):
+ console.get_style("foo bar")
+
+
+def test_render_error():
+ console = Console()
+ with pytest.raises(errors.NotRenderableError):
+ list(console.render([], console.options))
+
+
+def test_control():
+ console = Console(file=io.StringIO(), force_terminal=True)
+ console.control("FOO")
+ console.print("BAR")
+ assert console.file.getvalue() == "FOOBAR\n"
+
+
+def test_capture():
+ console = Console()
+ with console.capture() as capture:
+ with pytest.raises(CaptureError):
+ capture.get()
+ console.print("Hello")
+ assert capture.get() == "Hello\n"
+
+
+def test_input(monkeypatch, capsys):
+ def fake_input(prompt):
+ console.file.write(prompt)
+ return "bar"
+
+ monkeypatch.setattr("builtins.input", fake_input)
+ console = Console()
+ user_input = console.input(prompt="foo:")
+ assert capsys.readouterr().out == "foo:"
+ assert user_input == "bar"
+
+
+def test_input_legacy_windows(monkeypatch, capsys):
+ def fake_input(prompt):
+ console.file.write(prompt)
+ return "bar"
+
+ monkeypatch.setattr("builtins.input", fake_input)
+ console = Console(legacy_windows=True)
+ user_input = console.input(prompt="foo:")
+ assert capsys.readouterr().out == "foo:"
+ assert user_input == "bar"
+
+
+def test_input_password(monkeypatch, capsys):
+ def fake_input(prompt, stream=None):
+ console.file.write(prompt)
+ return "bar"
+
+ import rich.console
+
+ monkeypatch.setattr(rich.console, "getpass", fake_input)
+ console = Console()
+ user_input = console.input(prompt="foo:", password=True)
+ assert capsys.readouterr().out == "foo:"
+ assert user_input == "bar"
+
+
+def test_status():
+ console = Console(file=io.StringIO(), force_terminal=True, width=20)
+ status = console.status("foo")
+ assert isinstance(status, Status)
+
+
+def test_justify_none():
+ console = Console(file=io.StringIO(), force_terminal=True, width=20)
+ console.print("FOO", justify=None)
+ assert console.file.getvalue() == "FOO\n"
+
+
+def test_justify_left():
+ console = Console(file=io.StringIO(), force_terminal=True, width=20)
+ console.print("FOO", justify="left")
+ assert console.file.getvalue() == "FOO \n"
+
+
+def test_justify_center():
+ console = Console(file=io.StringIO(), force_terminal=True, width=20)
+ console.print("FOO", justify="center")
+ assert console.file.getvalue() == " FOO \n"
+
+
+def test_justify_right():
+ console = Console(file=io.StringIO(), force_terminal=True, width=20)
+ console.print("FOO", justify="right")
+ assert console.file.getvalue() == " FOO\n"
+
+
+def test_justify_renderable_none():
+ console = Console(
+ file=io.StringIO(), force_terminal=True, width=20, legacy_windows=False
+ )
+ console.print(Panel("FOO", expand=False, padding=0), justify=None)
+ assert console.file.getvalue() == "╭───╮\n│FOO│\n╰───╯\n"
+
+
+def test_justify_renderable_left():
+ console = Console(
+ file=io.StringIO(), force_terminal=True, width=10, legacy_windows=False
+ )
+ console.print(Panel("FOO", expand=False, padding=0), justify="left")
+ assert console.file.getvalue() == "╭───╮ \n│FOO│ \n╰───╯ \n"
+
+
+def test_justify_renderable_center():
+ console = Console(
+ file=io.StringIO(), force_terminal=True, width=10, legacy_windows=False
+ )
+ console.print(Panel("FOO", expand=False, padding=0), justify="center")
+ assert console.file.getvalue() == " ╭───╮ \n │FOO│ \n ╰───╯ \n"
+
+
+def test_justify_renderable_right():
+ console = Console(
+ file=io.StringIO(), force_terminal=True, width=20, legacy_windows=False
+ )
+ console.print(Panel("FOO", expand=False, padding=0), justify="right")
+ assert (
+ console.file.getvalue()
+ == " ╭───╮\n │FOO│\n ╰───╯\n"
+ )
+
+
+class BrokenRenderable:
+ def __rich_console__(self, console, options):
+ pass
+
+
+def test_render_broken_renderable():
+ console = Console()
+ broken = BrokenRenderable()
+ with pytest.raises(errors.NotRenderableError):
+ list(console.render(broken, console.options))
+
+
+def test_export_text():
+ console = Console(record=True, width=100)
+ console.print("[b]foo")
+ text = console.export_text()
+ expected = "foo\n"
+ assert text == expected
+
+
+def test_export_html():
+ console = Console(record=True, width=100)
+ console.print("[b]foo [link=https://example.org]Click[/link]")
+ html = console.export_html()
+ expected = '<!DOCTYPE html>\n<head>\n<meta charset="UTF-8">\n<style>\n.r1 {font-weight: bold}\nbody {\n color: #000000;\n background-color: #ffffff;\n}\n</style>\n</head>\n<html>\n<body>\n <code>\n <pre style="font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace"><span class="r1">foo </span><a href="https://example.org"><span class="r1">Click</span></a>\n</pre>\n </code>\n</body>\n</html>\n'
+ assert html == expected
+
+
+def test_export_html_inline():
+ console = Console(record=True, width=100)
+ console.print("[b]foo [link=https://example.org]Click[/link]")
+ html = console.export_html(inline_styles=True)
+ expected = '<!DOCTYPE html>\n<head>\n<meta charset="UTF-8">\n<style>\n\nbody {\n color: #000000;\n background-color: #ffffff;\n}\n</style>\n</head>\n<html>\n<body>\n <code>\n <pre style="font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace"><span style="font-weight: bold">foo </span><a href="https://example.org"><span style="font-weight: bold">Click</span></a>\n</pre>\n </code>\n</body>\n</html>\n'
+ assert html == expected
+
+
+def test_save_text():
+ console = Console(record=True, width=100)
+ console.print("foo")
+ with tempfile.TemporaryDirectory() as path:
+ export_path = os.path.join(path, "rich.txt")
+ console.save_text(export_path)
+ with open(export_path, "rt") as text_file:
+ assert text_file.read() == "foo\n"
+
+
+def test_save_html():
+ expected = "<!DOCTYPE html>\n<head>\n<meta charset=\"UTF-8\">\n<style>\n\nbody {\n color: #000000;\n background-color: #ffffff;\n}\n</style>\n</head>\n<html>\n<body>\n <code>\n <pre style=\"font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">foo\n</pre>\n </code>\n</body>\n</html>\n"
+ console = Console(record=True, width=100)
+ console.print("foo")
+ with tempfile.TemporaryDirectory() as path:
+ export_path = os.path.join(path, "example.html")
+ console.save_html(export_path)
+ with open(export_path, "rt") as html_file:
+ assert html_file.read() == expected
+
+
+def test_no_wrap():
+ console = Console(width=10, file=io.StringIO())
+ console.print("foo bar baz egg", no_wrap=True)
+ assert console.file.getvalue() == "foo bar ba\n"
+
+
+def test_soft_wrap():
+ console = Console(width=10, file=io.StringIO())
+ console.print("foo bar baz egg", soft_wrap=True)
+ assert console.file.getvalue() == "foo bar baz egg\n"
+
+
+def test_unicode_error() -> None:
+ try:
+ with tempfile.TemporaryFile("wt", encoding="ascii") as tmpfile:
+ console = Console(file=tmpfile)
+ console.print(":vampire:")
+ except UnicodeEncodeError as error:
+ assert "PYTHONIOENCODING" in str(error)
+ else:
+ assert False, "didn't raise UnicodeEncodeError"
+
+
+def test_bell() -> None:
+ console = Console(force_terminal=True)
+ console.begin_capture()
+ console.bell()
+ assert console.end_capture() == "\x07"
+
+
+def test_pager() -> None:
+ console = Console()
+
+ pager_content: Optional[str] = None
+
+ def mock_pager(content: str) -> None:
+ nonlocal pager_content
+ pager_content = content
+
+ pager = SystemPager()
+ pager._pager = mock_pager
+
+ with console.pager(pager):
+ console.print("[bold]Hello World")
+ assert pager_content == "Hello World\n"
+
+ with console.pager(pager, styles=True, links=False):
+ console.print("[bold link https:/example.org]Hello World")
+
+ assert pager_content == "Hello World\n"
+
+
+def test_out() -> None:
+ console = Console(width=10)
+ console.begin_capture()
+ console.out(*(["foo bar"] * 5), sep=".", end="X")
+ assert console.end_capture() == "foo bar.foo bar.foo bar.foo bar.foo barX"
+
+
+def test_render_group() -> None:
+ @render_group(fit=False)
+ def renderable():
+ yield "one"
+ yield "two"
+ yield "three" # <- largest width of 5
+ yield "four"
+
+ renderables = [renderable() for _ in range(4)]
+ console = Console(width=42)
+ min_width, _ = measure_renderables(console, renderables, 42)
+ assert min_width == 42
+
+
+def test_render_group_fit() -> None:
+ @render_group()
+ def renderable():
+ yield "one"
+ yield "two"
+ yield "three" # <- largest width of 5
+ yield "four"
+
+ renderables = [renderable() for _ in range(4)]
+
+ console = Console(width=42)
+
+ min_width, _ = measure_renderables(console, renderables, 42)
+ assert min_width == 5
+
+
+def test_get_time() -> None:
+ console = Console(
+ get_time=lambda: 99, get_datetime=lambda: datetime.datetime(1974, 7, 5)
+ )
+ assert console.get_time() == 99
+ assert console.get_datetime() == datetime.datetime(1974, 7, 5)
+
+
+def test_console_style() -> None:
+ console = Console(
+ file=io.StringIO(), color_system="truecolor", force_terminal=True, style="red"
+ )
+ console.print("foo")
+ expected = "\x1b[31mfoo\x1b[0m\n"
+ result = console.file.getvalue()
+ assert result == expected
+
+
+def test_no_color():
+ console = Console(
+ file=io.StringIO(), color_system="truecolor", force_terminal=True, no_color=True
+ )
+ console.print("[bold magenta on red]FOO")
+ expected = "\x1b[1mFOO\x1b[0m\n"
+ result = console.file.getvalue()
+ print(repr(result))
+ assert result == expected
+
+
+def test_quiet():
+ console = Console(file=io.StringIO(), quiet=True)
+ console.print("Hello, World!")
+ assert console.file.getvalue() == ""
+
+
+def test_no_nested_live():
+ console = Console()
+ with pytest.raises(errors.LiveError):
+ with console.status("foo"):
+ with console.status("bar"):
+ pass
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
+def test_screen():
+ console = Console(color_system=None, force_terminal=True, force_interactive=True)
+ with console.capture() as capture:
+ with console.screen():
+ console.print("Don't panic")
+ expected = "\x1b[?1049h\x1b[H\x1b[?25lDon't panic\n\x1b[?1049l\x1b[?25h"
+ result = capture.get()
+ print(repr(result))
+ assert result == expected
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
+def test_screen_update():
+ console = Console(width=20, height=4, color_system="truecolor", force_terminal=True)
+ with console.capture() as capture:
+ with console.screen() as screen:
+ screen.update("foo", style="blue")
+ screen.update("bar")
+ screen.update()
+ result = capture.get()
+ print(repr(result))
+ expected = "\x1b[?1049h\x1b[H\x1b[?25l\x1b[34mfoo\x1b[0m\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\x1b[34mbar\x1b[0m\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\x1b[34mbar\x1b[0m\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\n\x1b[34m \x1b[0m\x1b[?1049l\x1b[?25h"
+ assert result == expected
+
+
+def test_height():
+ console = Console(width=80, height=46)
+ assert console.height == 46