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 = '\n\n\n\n\n\n\n \n
foo Click\n
\n
\n\n\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 = '\n\n\n\n\n\n\n \n
foo Click\n
\n
\n\n\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 = "\n\n\n\n\n\n\n \n
foo\n
\n
\n\n\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