"""Robot Framework helper for output rendering end-to-end tests.""" from __future__ import annotations import json import re import sys from pathlib import Path import yaml # Ensure the local source tree is importable _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) from cleveragents.cli.formatting import format_output # noqa: E402 from cleveragents.cli.output.handles import DiffLine # noqa: E402 from cleveragents.cli.output.materializers import ( # noqa: E402 ColorMaterializer, JsonMaterializer, PlainMaterializer, RichMaterializer, TableMaterializer, YamlMaterializer, ) from cleveragents.cli.output.selection import ( # noqa: E402 TerminalCapabilities, select_materializer, ) from cleveragents.cli.output.session import OutputSession # noqa: E402 ANSI_RE = re.compile(r"\033\[") def test_plain_panel() -> None: """Plain format renders panel without ANSI codes.""" strategy = PlainMaterializer() with OutputSession(format="plain", command="test", strategy=strategy) as session: panel = session.panel("Project Info") panel.set_entry("Name", "my-project") panel.set_entry("Version", "1.0.0") panel.close() output = strategy.get_output() assert "Project Info" in output assert "Name: my-project" in output assert not ANSI_RE.search(output), "Plain output should not contain ANSI codes" print("output-rendering-plain-panel-ok") def test_json_output() -> None: """JSON format produces valid JSON with elements.""" strategy = JsonMaterializer() with OutputSession(format="json", command="test", strategy=strategy) as session: panel = session.panel("Details") panel.set_entry("Key", "Value") panel.close() table = session.table(columns=["Col1", "Col2"]) table.add_row({"Col1": "a", "Col2": "b"}) table.close() output = strategy.get_output() data = json.loads(output) assert "data" in data assert len(data["data"]) == 2 assert "status" in data assert data["status"] == "ok" print("output-rendering-json-output-ok") def test_yaml_output() -> None: """YAML format produces valid YAML with elements.""" strategy = YamlMaterializer() with OutputSession(format="yaml", command="test", strategy=strategy) as session: panel = session.panel("YAML Details") panel.set_entry("Alpha", "Beta") panel.close() output = strategy.get_output() data = yaml.safe_load(output) assert "data" in data assert "status" in data assert data["status"] == "ok" print("output-rendering-yaml-output-ok") def test_table_boxdraw() -> None: """Table format uses Unicode box-drawing characters (P2-5 upgrade).""" strategy = TableMaterializer() with OutputSession(format="table", command="test", strategy=strategy) as session: table = session.table(columns=["Name", "Status"]) table.add_row({"Name": "svc", "Status": "up"}) table.close() output = strategy.get_output() assert "│" in output or "╭" in output, ( "Table output should contain Unicode box chars" ) assert "svc" in output print("output-rendering-table-boxdraw-ok") def test_color_ansi() -> None: """Color format includes ANSI escape codes.""" strategy = ColorMaterializer() with OutputSession(format="color", command="test", strategy=strategy) as session: panel = session.panel("Coloured Title") panel.set_entry("Key", "val") panel.close() output = strategy.get_output() assert ANSI_RE.search(output), "Color output should contain ANSI codes" print("output-rendering-color-ansi-ok") def test_json_error() -> None: """Error envelope in JSON mode has correct structure.""" mat = JsonMaterializer() output = mat.get_error_output("NOT_FOUND", "Item not found", {"id": "123"}) data = json.loads(output) assert "error" in data assert data["error"]["code"] == "NOT_FOUND" assert data["error"]["message"] == "Item not found" assert data["error"]["details"]["id"] == "123" print("output-rendering-json-error-ok") def test_backward_compat() -> None: """format_output still works for all formats. For machine-readable formats (json, yaml, plain) ``format_output`` writes directly to ``sys.stdout`` and returns ``""``. We capture stdout so the assertion checks the *produced* output rather than just the return value. """ import sys as _sys from io import StringIO as _SIO data = {"Name": "test-proj", "Status": "active"} for fmt in ("json", "yaml", "plain", "table", "rich"): buf = _SIO() old_stdout = _sys.stdout _sys.stdout = buf try: result = format_output(data, fmt) finally: _sys.stdout = old_stdout # Combine: either the return value has content, or stdout was written to effective = result or buf.getvalue().rstrip("\n") assert effective, f"format_output({fmt}) produced no output" print("output-rendering-backward-compat-ok") def test_selection_fallback() -> None: """Materializer selection with fallback logic.""" no_tty = TerminalCapabilities( is_tty=False, supports_ansi=False, supports_cursor=False, term="" ) # Rich falls back to plain when no TTY mat = select_materializer("rich", capabilities=no_tty) assert isinstance(mat, PlainMaterializer) # JSON always works mat = select_materializer("json", capabilities=no_tty) assert isinstance(mat, JsonMaterializer) # Explicit overrides fallback mat = select_materializer("rich", capabilities=no_tty, explicit=True) assert isinstance(mat, RichMaterializer) print("output-rendering-selection-fallback-ok") def test_tree_rendering() -> None: """Tree element renders across all formats.""" for fmt, cls in [("plain", PlainMaterializer), ("json", JsonMaterializer)]: strategy = cls() with OutputSession(format=fmt, command="bench", strategy=strategy) as session: tree = session.tree("Root") tree.add_child("Root", "src") tree.add_child("Root", "tests") tree.add_child("Root/src", "main.py") tree.close() output = strategy.get_output() assert "Root" in output, f"Tree root not in {fmt} output" assert "src" in output, f"Tree child 'src' not in {fmt} output" print("output-rendering-tree-ok") def test_code_rendering() -> None: """Code element renders with line numbers.""" strategy = PlainMaterializer() with OutputSession(format="plain", command="bench", strategy=strategy) as session: code = session.code("x = 1\ny = 2", language="python", line_numbers=True) code.close() output = strategy.get_output() assert "1 |" in output, "Code should have line numbers" assert "x = 1" in output print("output-rendering-code-ok") def test_diff_rendering() -> None: """Diff element renders unified diff format.""" strategy = PlainMaterializer() with OutputSession(format="plain", command="bench", strategy=strategy) as session: diff = session.diff(file_a="old.py", file_b="new.py") diff.add_hunk( "@@ -1,3 +1,4 @@", [ DiffLine(type="context", content="import os"), DiffLine(type="remove", content="x = 1"), DiffLine(type="add", content="x = 2"), DiffLine(type="add", content="y = 3"), ], ) diff.set_stats(2, 1) diff.close() output = strategy.get_output() assert "--- old.py" in output assert "+++ new.py" in output assert "+x = 2" in output assert "-x = 1" in output assert "2 insertion(s)" in output print("output-rendering-diff-ok") def test_text_separator_hint() -> None: """Text, separator, and action hint render correctly.""" strategy = PlainMaterializer() with OutputSession(format="plain", command="bench", strategy=strategy) as session: text = session.text("Hello World", indent=2) text.close() session.separator("line") session.action_hint(["agents list", "agents deploy"], "Try these:") output = strategy.get_output() assert "Hello World" in output assert "----" in output assert "agents list" in output print("output-rendering-text-sep-hint-ok") def test_json_all_elements() -> None: """JSON format serializes all 10 element types.""" strategy = JsonMaterializer() with OutputSession(format="json", command="all", strategy=strategy) as session: # Panel p = session.panel("P") p.set_entry("k", "v") p.close() # Table t = session.table(columns=["A"]) t.add_row({"A": "1"}) t.close() # Status s = session.status("ok") s.close() # Progress pr = session.progress("go", total=10) pr.set_progress(5, 10) pr.close() # Tree tr = session.tree("Root") tr.add_child("Root", "leaf") tr.close() # Text tx = session.text("hello") tx.close() # Code co = session.code("x=1", language="py") co.close() # Diff d = session.diff(file_a="a", file_b="b") d.add_hunk("@@", [DiffLine(type="add", content="new")]) d.close() # Separator session.separator("line") # ActionHint session.action_hint(["cmd"]) output = strategy.get_output() data = json.loads(output) types = [e["type"] for e in data["data"]] expected = [ "panel", "table", "status", "progress", "tree", "text", "code", "diff", "separator", "action_hint", ] assert types == expected, f"Expected {expected}, got {types}" print("output-rendering-json-all-ok") def test_yaml_all_elements() -> None: """YAML format serializes all 10 element types (M5 fix — Luis review).""" strategy = YamlMaterializer() with OutputSession(format="yaml", command="all", strategy=strategy) as session: # Panel p = session.panel("P") p.set_entry("k", "v") p.close() # Table t = session.table(columns=["A"]) t.add_row({"A": "1"}) t.close() # Status s = session.status("ok") s.close() # Progress pr = session.progress("go", total=10) pr.set_progress(5, 10) pr.close() # Tree tr = session.tree("Root") tr.add_child("Root", "leaf") tr.close() # Text tx = session.text("hello") tx.close() # Code co = session.code("x=1", language="py") co.close() # Diff d = session.diff(file_a="a", file_b="b") d.add_hunk("@@", [DiffLine(type="add", content="new")]) d.close() # Separator session.separator("line") # ActionHint session.action_hint(["cmd"]) output = strategy.get_output() data = yaml.safe_load(output) types = [e["type"] for e in data["data"]] expected = [ "panel", "table", "status", "progress", "tree", "text", "code", "diff", "separator", "action_hint", ] assert types == expected, f"Expected {expected}, got {types}" print("output-rendering-yaml-all-ok") _DISPATCH = { "plain-panel": test_plain_panel, "json-output": test_json_output, "yaml-output": test_yaml_output, "table-boxdraw": test_table_boxdraw, "color-ansi": test_color_ansi, "json-error": test_json_error, "backward-compat": test_backward_compat, "selection-fallback": test_selection_fallback, "tree": test_tree_rendering, "code": test_code_rendering, "diff": test_diff_rendering, "text-sep-hint": test_text_separator_hint, "json-all": test_json_all_elements, "yaml-all": test_yaml_all_elements, } if __name__ == "__main__": if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} ", file=sys.stderr) sys.exit(1) test_name = sys.argv[1] if test_name not in _DISPATCH: print(f"Unknown test: {test_name}", file=sys.stderr) sys.exit(1) _DISPATCH[test_name]()