import pytest
from rich.console import Console
from rich.style import Style
from rich.text import Text
from conftest import (
run_richrs_test,
capture_rich_ansi,
compare_ansi_output,
normalize_ansi,
)
class TestStyleComparison:
@pytest.mark.parametrize(
"style_str",
[
"bold",
"italic",
"underline",
"strike",
"dim",
"reverse",
"blink",
"conceal",
"not bold",
"not italic",
"bold italic",
"bold italic underline",
],
)
def test_style_attributes(self, style_str: str, richrs_runner):
py_style = Style.parse(style_str)
py_data = {
"bold": py_style.bold,
"italic": py_style.italic,
"underline": py_style.underline,
"strike": py_style.strike,
"dim": py_style.dim,
"reverse": py_style.reverse,
"blink": py_style.blink,
"conceal": py_style.conceal,
}
rust_result = run_richrs_test("style_parse", {"style": style_str})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_data = rust_result["data"]
assert py_data["bold"] == rust_data["bold"], f"bold mismatch for '{style_str}'"
assert py_data["italic"] == rust_data["italic"], f"italic mismatch for '{style_str}'"
assert py_data["underline"] == rust_data["underline"], f"underline mismatch for '{style_str}'"
assert py_data["strike"] == rust_data["strike"], f"strike mismatch for '{style_str}'"
@pytest.mark.parametrize(
"style_str",
[
"red",
"green",
"blue",
"yellow",
"magenta",
"cyan",
"white",
"black",
],
)
def test_basic_colors(self, style_str: str, richrs_runner):
py_style = Style.parse(style_str)
py_color_name = py_style.color.name if py_style.color else None
rust_result = run_richrs_test("style_parse", {"style": style_str})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_color = rust_result["data"]["color"]
assert py_color_name is not None, "Python should parse color"
assert rust_color is not None, "Rust should parse color"
assert py_color_name.lower() == rust_color.lower(), f"color mismatch for '{style_str}': Python={py_color_name}, Rust={rust_color}"
@pytest.mark.parametrize(
"style_str",
[
"bright_red",
"bright_green",
"bright_blue",
],
)
def test_bright_colors(self, style_str: str, richrs_runner):
py_style = Style.parse(style_str)
rust_result = run_richrs_test("style_parse", {"style": style_str})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
@pytest.mark.parametrize(
"style_str",
[
"red on white",
"blue on black",
"bold red on blue",
],
)
def test_color_on_bgcolor(self, style_str: str, richrs_runner):
py_style = Style.parse(style_str)
rust_result = run_richrs_test("style_parse", {"style": style_str})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
assert rust_result["data"]["color"] is not None, "Rust should have fg color"
assert rust_result["data"]["bgcolor"] is not None, "Rust should have bg color"
@pytest.mark.parametrize(
"hex_color",
[
"#ff0000",
"#00ff00",
"#0000ff",
"#ffffff",
"#000000",
],
)
def test_hex_colors(self, hex_color: str, richrs_runner):
py_style = Style.parse(hex_color)
rust_result = run_richrs_test("style_parse", {"style": hex_color})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
assert rust_result["data"]["color"] is not None
@pytest.mark.parametrize(
"short_hex",
[
"#f00",
"#0f0",
"#00f",
],
)
def test_short_hex_colors_rust_only(self, short_hex: str, richrs_runner):
rust_result = run_richrs_test("style_parse", {"style": short_hex})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
assert rust_result["data"]["color"] is not None
class TestRgbColorsRustOnly:
@pytest.mark.parametrize(
"rgb_color",
[
"rgb(255, 0, 0)",
"rgb(0, 255, 0)",
"rgb(0, 0, 255)",
"rgb(128, 128, 128)",
],
)
def test_rgb_colors_rust_only(self, rgb_color: str, richrs_runner):
rust_result = run_richrs_test("style_parse", {"style": rgb_color})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
assert rust_result["data"]["color"] is not None
class TestStyleRenderComparison:
@pytest.mark.parametrize(
"style_str,text",
[
("bold", "Hello"),
("italic", "World"),
("underline", "Test"),
("red", "Red"),
("bold red", "Bold Red"),
("italic blue on white", "Blue on White"),
],
)
def test_style_render_output(self, style_str: str, text: str, richrs_runner):
py_text = Text(text, style=style_str)
py_output = capture_rich_ansi(py_text)
rust_result = run_richrs_test("style_render", {"style": style_str, "text": text})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None, "Rust should produce output"
assert len(rust_output) > 0, "Rust output should not be empty"
class TestTextComparison:
@pytest.mark.parametrize(
"text,style",
[
("Hello World", None),
("Styled Text", "bold"),
("Colored", "red"),
("Combined", "bold italic red"),
],
)
def test_text_render(self, text: str, style: str | None, richrs_runner):
if style:
py_text = Text(text, style=style)
else:
py_text = Text(text)
py_output = capture_rich_ansi(py_text)
test_data = {"text": text}
if style:
test_data["style"] = style
rust_result = run_richrs_test("text_render", test_data)
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None, "Rust should produce output"
class TestColorComparison:
@pytest.mark.parametrize(
"color_str",
[
"red",
"green",
"blue",
"#ff0000",
"#00ff00",
"rgb(255, 128, 64)",
"color(5)",
],
)
def test_color_parse(self, color_str: str, richrs_runner):
from rich.color import Color as RichColor
py_color = RichColor.parse(color_str)
rust_result = run_richrs_test("color_parse", {"color": color_str})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
assert rust_result["data"] is not None
class TestConsoleMarkupComparison:
@pytest.mark.parametrize(
"markup",
[
"[bold]Bold Text[/]",
"[italic]Italic Text[/]",
"[red]Red Text[/]",
"[bold red]Bold Red[/]",
"[bold]Bold [italic]and Italic[/italic][/bold]",
"Normal [bold]Bold[/] Normal",
],
)
def test_markup_render(self, markup: str, richrs_runner):
from io import StringIO
from rich.console import Console
output = StringIO()
console = Console(
file=output,
width=80,
force_terminal=True,
color_system="truecolor",
)
console.print(markup, end="")
py_output = output.getvalue()
rust_result = run_richrs_test("console_render", {"markup": markup})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None, "Rust should produce output"
assert len(rust_output) > 0, "Rust output should not be empty"
class TestTableComparison:
def test_simple_table(self, richrs_runner):
from rich.table import Table as RichTable
width = 60
py_table = RichTable()
py_table.add_column("Name")
py_table.add_column("Value")
py_table.add_row("Alice", "100")
py_table.add_row("Bob", "200")
py_output = capture_rich_ansi(py_table, width=width)
rust_result = run_richrs_test(
"table_render",
{
"columns": ["Name", "Value"],
"rows": [["Alice", "100"], ["Bob", "200"]],
"width": width,
},
)
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert "Alice" in rust_output
assert "Bob" in rust_output
def test_three_column_table(self, richrs_runner):
width = 80
rust_result = run_richrs_test(
"table_render",
{
"columns": ["ID", "Name", "Status"],
"rows": [["1", "Item A", "Active"], ["2", "Item B", "Inactive"]],
"width": width,
},
)
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert "Item A" in rust_output
assert "Item B" in rust_output
class TestRuleComparison:
def test_simple_rule(self, richrs_runner):
from rich.rule import Rule as RichRule
width = 60
py_rule = RichRule()
py_output = capture_rich_ansi(py_rule, width=width)
rust_result = run_richrs_test("rule_render", {"width": width})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert len(rust_output) > 0
def test_rule_with_title(self, richrs_runner):
from rich.rule import Rule as RichRule
title = "Section Title"
width = 60
py_rule = RichRule(title)
py_output = capture_rich_ansi(py_rule, width=width)
rust_result = run_richrs_test("rule_render", {"title": title, "width": width})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert title in rust_output
class TestTreeComparison:
def test_simple_tree(self, richrs_runner):
from rich.tree import Tree as RichTree
py_tree = RichTree("root")
py_tree.add("child1")
py_tree.add("child2")
py_output = capture_rich_ansi(py_tree, width=60)
rust_result = run_richrs_test(
"tree_render", {"label": "root", "children": ["child1", "child2"]}
)
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert "root" in rust_output
assert "child1" in rust_output
assert "child2" in rust_output
def test_tree_no_children(self, richrs_runner):
rust_result = run_richrs_test("tree_render", {"label": "lonely_root"})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert "lonely_root" in rust_output
class TestPanelComparison:
def test_simple_panel(self, richrs_runner):
from rich.panel import Panel as RichPanel
content = "Hello, World!"
width = 40
py_panel = RichPanel(content)
py_output = capture_rich_ansi(py_panel, width=width)
rust_result = run_richrs_test("panel_render", {"content": content, "width": width})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert len(rust_output) > 0
def test_panel_with_title(self, richrs_runner):
from rich.panel import Panel as RichPanel
content = "Content here"
title = "Title"
width = 40
py_panel = RichPanel(content, title=title)
py_output = capture_rich_ansi(py_panel, width=width)
rust_result = run_richrs_test(
"panel_render", {"content": content, "title": title, "width": width}
)
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
assert title in rust_output or "Title" in rust_output
def test_panel_with_subtitle(self, richrs_runner):
from rich.panel import Panel as RichPanel
content = "Content here"
subtitle = "Subtitle"
width = 40
py_panel = RichPanel(content, subtitle=subtitle)
py_output = capture_rich_ansi(py_panel, width=width)
rust_result = run_richrs_test(
"panel_render", {"content": content, "subtitle": subtitle, "width": width}
)
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_output = rust_result["output"]
assert rust_output is not None
class TestEmojiComparison:
@pytest.mark.parametrize(
"emoji_name",
[
"smile",
"thumbs_up",
"rocket",
"fire",
"star",
"warning",
"heart",
"coffee",
"bug",
"gear",
"lock",
"key",
"bulb",
"sparkles",
"zap",
"white_check_mark",
"x",
],
)
def test_emoji_lookup(self, emoji_name: str, richrs_runner):
from rich.emoji import Emoji as RichEmoji
py_emoji = RichEmoji(emoji_name)
py_char = str(py_emoji)
rust_result = run_richrs_test("emoji_lookup", {"name": emoji_name})
assert rust_result["success"], f"Rust failed: {rust_result.get('error')}"
rust_char = rust_result["output"]
assert py_char == rust_char, f"Emoji mismatch for '{emoji_name}': Python={py_char!r}, Rust={rust_char!r}"
def test_emoji_not_found(self, richrs_runner):
rust_result = run_richrs_test("emoji_lookup", {"name": "nonexistent_emoji_xyz"})
assert not rust_result["success"], "Rust should fail for nonexistent emoji"
if __name__ == "__main__":
pytest.main([__file__, "-v"])