from io import StringIO import pytest from rich.console import Console from rich.text import Span, Text from rich.measure import Measurement from rich.style import Style def test_span(): span = Span(1, 10, "foo") repr(span) assert bool(span) assert not Span(10, 10, "foo") def test_span_split(): assert Span(5, 10, "foo").split(2) == (Span(5, 10, "foo"), None) assert Span(5, 10, "foo").split(15) == (Span(5, 10, "foo"), None) assert Span(0, 10, "foo").split(5) == (Span(0, 5, "foo"), Span(5, 10, "foo")) def test_span_move(): assert Span(5, 10, "foo").move(2) == Span(7, 12, "foo") def test_span_right_crop(): assert Span(5, 10, "foo").right_crop(15) == Span(5, 10, "foo") assert Span(5, 10, "foo").right_crop(7) == Span(5, 7, "foo") def test_len(): assert len(Text("foo")) == 3 def test_cell_len(): assert Text("foo").cell_len == 3 assert Text("šŸ˜€").cell_len == 2 def test_bool(): assert Text("foo") assert not Text("") def test_str(): assert str(Text("foo")) == "foo" def test_repr(): assert isinstance(repr(Text("foo")), str) def test_add(): text = Text("foo") + Text("bar") assert str(text) == "foobar" assert Text("foo").__add__(1) == NotImplemented def test_eq(): assert Text("foo") == Text("foo") assert Text("foo") != Text("bar") assert Text("foo").__eq__(1) == NotImplemented def test_contain(): test = Text("foobar") assert "foo" in test assert "foo " not in test assert Text("bar") in test assert None not in test def test_plain_property(): text = Text("foo") text.append("bar") text.append("baz") assert text.plain == "foobarbaz" def test_plain_property_setter(): test = Text("foo") test.plain = "bar" assert str(test) == "bar" test = Text() test.append("Hello, World", "bold") test.plain = "Hello" assert str(test) == "Hello" assert test._spans == [Span(0, 5, "bold")] def test_from_markup(): text = Text.from_markup("Hello, [bold]World![/bold]") assert str(text) == "Hello, World!" assert text._spans == [Span(7, 13, "bold")] def test_copy(): test = Text() test.append("Hello", "bold") test.append(" ") test.append("World", "italic") test_copy = test.copy() assert test == test_copy assert test is not test_copy def test_rstrip(): test = Text("Hello, World! ") test.rstrip() assert str(test) == "Hello, World!" def test_rstrip_end(): test = Text("Hello, World! ") test.rstrip_end(14) assert str(test) == "Hello, World! " def test_stylize(): test = Text("Hello, World!") test.stylize("bold", 7, 11) assert test._spans == [Span(7, 11, "bold")] test.stylize("bold", 20, 25) assert test._spans == [Span(7, 11, "bold")] def test_stylize_negative_index(): test = Text("Hello, World!") test.stylize("bold", -6, -1) assert test._spans == [Span(7, 12, "bold")] def test_highlight_regex(): test = Text("peek-a-boo") count = test.highlight_regex(r"NEVER_MATCH", "red") assert count == 0 assert len(test._spans) == 0 # text: peek-a-boo # indx: 0123456789 count = test.highlight_regex(r"[a|e|o]+", "red") assert count == 3 assert sorted(test._spans) == [ Span(1, 3, "red"), Span(5, 6, "red"), Span(8, 10, "red"), ] test = Text("Ada Lovelace, Alan Turing") count = test.highlight_regex( r"(?P[A-Za-z]+)[ ]+(?P[A-Za-z]+)(?PNEVER_MATCH)*" ) # The number of matched name should be 2 assert count == 2 assert sorted(test._spans) == [ Span(0, 3, "yellow"), # Ada Span(4, 12, "red"), # Lovelace Span(14, 18, "yellow"), # Alan Span(19, 25, "red"), # Turing ] def test_highlight_regex_callable(): test = Text("Vulnerability CVE-2018-6543 detected") re_cve = r"CVE-\d{4}-\d+" def get_style(text: str) -> Style: return Style.parse( f"bold yellow link https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword={text}" ) count = test.highlight_regex(re_cve, get_style) assert count == 1 assert len(test._spans) == 1 assert test._spans[0].start == 14 assert test._spans[0].end == 27 assert ( test._spans[0].style.link == "https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=CVE-2018-6543" ) def test_highlight_words(): test = Text("Do NOT! touch anything!") words = ["NOT", "!"] count = test.highlight_words(words, "red") assert count == 3 assert sorted(test._spans) == [ Span(3, 6, "red"), # NOT Span(6, 7, "red"), # ! Span(22, 23, "red"), # ! ] # regex escape test test = Text("[o|u]aeiou") words = ["[a|e|i]", "[o|u]"] count = test.highlight_words(words, "red") assert count == 1 assert test._spans == [Span(0, 5, "red")] # case sensitive test = Text("AB Ab aB ab") words = ["AB"] count = test.highlight_words(words, "red") assert count == 1 assert test._spans == [Span(0, 2, "red")] test = Text("AB Ab aB ab") count = test.highlight_words(words, "red", case_sensitive=False) assert count == 4 def test_set_length(): test = Text("Hello") test.set_length(5) assert test == Text("Hello") test = Text("Hello") test.set_length(10) assert test == Text("Hello ") test = Text("Hello World") test.stylize("bold", 0, 5) test.stylize("italic", 7, 9) test.set_length(3) expected = Text() expected.append("Hel", "bold") assert test == expected def test_console_width(): console = Console() test = Text("Hello World!\nfoobarbaz") assert test.__rich_measure__(console, 80) == Measurement(9, 12) assert Text(" " * 4).__rich_measure__(console, 80) == Measurement(4, 4) def test_join(): test = Text("bar").join([Text("foo", "red"), Text("baz", "blue")]) assert str(test) == "foobarbaz" assert test._spans == [Span(0, 3, "red"), Span(3, 6, ""), Span(6, 9, "blue")] def test_trim_spans(): test = Text("Hello") test._spans[:] = [Span(0, 3, "red"), Span(3, 6, "green"), Span(6, 9, "blue")] test._trim_spans() assert test._spans == [Span(0, 3, "red"), Span(3, 5, "green")] def test_pad_left(): test = Text("foo") test.pad_left(3, "X") assert str(test) == "XXXfoo" def test_pad_right(): test = Text("foo") test.pad_right(3, "X") assert str(test) == "fooXXX" def test_append(): test = Text("foo") test.append("bar") assert str(test) == "foobar" test.append(Text("baz", "bold")) assert str(test) == "foobarbaz" assert test._spans == [Span(6, 9, "bold")] with pytest.raises(ValueError): test.append(Text("foo"), "bar") with pytest.raises(TypeError): test.append(1) def test_append_text(): test = Text("foo") test.append_text(Text("bar", style="bold")) assert str(test) == "foobar" assert test._spans == [Span(3, 6, "bold")] def test_split(): test = Text() test.append("foo", "red") test.append("\n") test.append("bar", "green") test.append("\n") line1 = Text() line1.append("foo", "red") line2 = Text() line2.append("bar", "green") split = test.split("\n") assert len(split) == 2 assert split[0] == line1 assert split[1] == line2 assert list(Text("foo").split("\n")) == [Text("foo")] def test_divide(): lines = Text("foo").divide([]) assert len(lines) == 1 assert lines[0] == Text("foo") text = Text() text.append("foo", "bold") lines = text.divide([1, 2]) assert len(lines) == 3 assert str(lines[0]) == "f" assert str(lines[1]) == "o" assert str(lines[2]) == "o" assert lines[0]._spans == [Span(0, 1, "bold")] assert lines[1]._spans == [Span(0, 1, "bold")] assert lines[2]._spans == [Span(0, 1, "bold")] text = Text() text.append("foo", "red") text.append("bar", "green") text.append("baz", "blue") lines = text.divide([8]) assert len(lines) == 2 assert str(lines[0]) == "foobarba" assert str(lines[1]) == "z" assert lines[0]._spans == [ Span(0, 3, "red"), Span(3, 6, "green"), Span(6, 8, "blue"), ] assert lines[1]._spans == [Span(0, 1, "blue")] lines = text.divide([1]) assert len(lines) == 2 assert str(lines[0]) == "f" assert str(lines[1]) == "oobarbaz" assert lines[0]._spans == [Span(0, 1, "red")] assert lines[1]._spans == [ Span(0, 2, "red"), Span(2, 5, "green"), Span(5, 8, "blue"), ] def test_right_crop(): test = Text() test.append("foobar", "red") test.right_crop(3) assert str(test) == "foo" assert test._spans == [Span(0, 3, "red")] def test_wrap_3(): test = Text("foo bar baz") lines = test.wrap(Console(), 3) print(repr(lines)) assert len(lines) == 3 assert lines[0] == Text("foo") assert lines[1] == Text("bar") assert lines[2] == Text("baz") def test_wrap_4(): test = Text("foo bar baz", justify="left") lines = test.wrap(Console(), 4) assert len(lines) == 3 assert lines[0] == Text("foo ") assert lines[1] == Text("bar ") assert lines[2] == Text("baz ") def test_wrap_long(): test = Text("abracadabra", justify="left") lines = test.wrap(Console(), 4) assert len(lines) == 3 assert lines[0] == Text("abra") assert lines[1] == Text("cada") assert lines[2] == Text("bra ") def test_wrap_overflow(): test = Text("Some more words") lines = test.wrap(Console(), 4, overflow="ellipsis") assert (len(lines)) == 3 assert lines[0] == Text("Some") assert lines[1] == Text("more") assert lines[2] == Text("worā€¦") def test_wrap_overflow_long(): test = Text("bigword" * 10) lines = test.wrap(Console(), 4, overflow="ellipsis") assert len(lines) == 1 assert lines[0] == Text("bigā€¦") def test_wrap_long_words(): test = Text("X 123456789", justify="left") lines = test.wrap(Console(), 4) assert len(lines) == 3 assert lines[0] == Text("X 12") assert lines[1] == Text("3456") assert lines[2] == Text("789 ") def test_no_wrap_no_crop(): test = Text("Hello World!" * 3) console = Console(width=20, file=StringIO()) console.print(test, no_wrap=True) console.print(test, no_wrap=True, crop=False, overflow="ignore") print(repr(console.file.getvalue())) assert ( console.file.getvalue() == "Hello World!Hello Wo\nHello World!Hello World!Hello World!\n" ) def test_fit(): test = Text("Hello\nWorld") lines = test.fit(3) assert str(lines[0]) == "Hel" assert str(lines[1]) == "Wor" def test_wrap_tabs(): test = Text("foo\tbar", justify="left") lines = test.wrap(Console(), 4) assert len(lines) == 2 assert str(lines[0]) == "foo " assert str(lines[1]) == "bar " def test_render(): console = Console(width=15, record=True) test = Text.from_markup( "[u][b]Where[/b] there is a [i]Will[/i], there is a Way.[/u]" ) console.print(test) output = console.export_text(styles=True) expected = "\x1b[1;4mWhere\x1b[0m\x1b[4m there is \x1b[0m\n\x1b[4ma \x1b[0m\x1b[3;4mWill\x1b[0m\x1b[4m, there \x1b[0m\n\x1b[4mis a Way.\x1b[0m\n" assert output == expected @pytest.mark.parametrize( "print_text,result", [ (("."), ".\n"), ((".", "."), ". .\n"), (("Hello", "World", "!"), "Hello World !\n"), ], ) def test_print(print_text, result): console = Console(record=True) console.print(*print_text) assert console.export_text(styles=False) == result @pytest.mark.parametrize( "print_text,result", [ (("."), ".X"), ((".", "."), "..X"), (("Hello", "World", "!"), "HelloWorld!X"), ], ) def test_print_sep_end(print_text, result): console = Console(record=True, file=StringIO()) console.print(*print_text, sep="", end="X") assert console.file.getvalue() == result def test_tabs_to_spaces(): test = Text("\tHello\tWorld", tab_size=8) test.expand_tabs() assert test.plain == " Hello World" test = Text("\tHello\tWorld", tab_size=4) test.expand_tabs() assert test.plain == " Hello World" test = Text(".\t..\t...\t....\t", tab_size=4) test.expand_tabs() assert test.plain == ". .. ... .... " test = Text("No Tabs") test.expand_tabs() assert test.plain == "No Tabs" def test_markup_switch(): """Test markup can be disabled.""" console = Console(file=StringIO(), markup=False) console.print("[bold]foo[/bold]") assert console.file.getvalue() == "[bold]foo[/bold]\n" def test_emoji(): """Test printing emoji codes.""" console = Console(file=StringIO()) console.print(":+1:") assert console.file.getvalue() == "šŸ‘\n" def test_emoji_switch(): """Test emoji can be disabled.""" console = Console(file=StringIO(), emoji=False) console.print(":+1:") assert console.file.getvalue() == ":+1:\n" def test_assemble(): text = Text.assemble("foo", ("bar", "bold")) assert str(text) == "foobar" assert text._spans == [Span(3, 6, "bold")] def test_styled(): text = Text.styled("foo", "bold red") assert text.style == "" assert str(text) == "foo" assert text._spans == [Span(0, 3, "bold red")] def test_strip_control_codes(): text = Text("foo\rbar") assert str(text) == "foobar" text.append("\x08") assert str(text) == "foobar" def test_get_style_at_offset(): console = Console() text = Text.from_markup("Hello [b]World[/b]") assert text.get_style_at_offset(console, 0) == Style() assert text.get_style_at_offset(console, 6) == Style(bold=True) @pytest.mark.parametrize( "input, count, expected", [ ("Hello", 10, "Hello"), ("Hello", 5, "Hello"), ("Hello", 4, "Helā€¦"), ("Hello", 3, "Heā€¦"), ("Hello", 2, "Hā€¦"), ("Hello", 1, "ā€¦"), ], ) def test_truncate_ellipsis(input, count, expected): text = Text(input) text.truncate(count, overflow="ellipsis") assert text.plain == expected @pytest.mark.parametrize( "input, count, expected", [ ("Hello", 5, "Hello"), ("Hello", 10, "Hello "), ("Hello", 3, "Heā€¦"), ], ) def test_truncate_ellipsis_pad(input, count, expected): text = Text(input) text.truncate(count, overflow="ellipsis", pad=True) assert text.plain == expected def test_pad(): test = Text("foo") test.pad(2) assert test.plain == " foo " def test_align_left(): test = Text("foo") test.align("left", 10) assert test.plain == "foo " def test_align_right(): test = Text("foo") test.align("right", 10) assert test.plain == " foo" def test_align_center(): test = Text("foo") test.align("center", 10) assert test.plain == " foo "