"""Tests for ExcelRenderer — Markdown/JSON → .xlsx mapping (U3).""" from __future__ import annotations import json from pathlib import Path from openpyxl import load_workbook from agentkit.documents.renderers.excel_renderer import ExcelRenderer def _render(content: str, tmp_path: Path) -> Path: out = tmp_path / "test.xlsx" ExcelRenderer().render(content, out) return out def _read_workbook(path: Path) -> dict[str, list[list[str]]]: """Return {sheet_name: [[row cells], ...]} from a .xlsx file.""" wb = load_workbook(str(path)) result: dict[str, list[list[str]]] = {} for ws in wb.worksheets: rows: list[list[str]] = [] for row in ws.iter_rows(values_only=True): rows.append([str(c) if c is not None else "" for c in row]) result[ws.title] = rows return result def test_markdown_single_table(tmp_path: Path) -> None: """A single GFM table becomes a Table1 sheet with correct data.""" md = "| Name | Age |\n| --- | --- |\n| Alice | 30 |\n| Bob | 25 |\n" path = _render(md, tmp_path) sheets = _read_workbook(path) assert "Table1" in sheets rows = sheets["Table1"] assert rows[0] == ["Name", "Age"] assert rows[1] == ["Alice", "30"] assert rows[2] == ["Bob", "25"] def test_markdown_multiple_tables(tmp_path: Path) -> None: """Multiple GFM tables become separate sheets (Table1, Table2).""" md = ( "| A | B |\n| --- | --- |\n| 1 | 2 |\n\n" "Some text between.\n\n" "| C | D |\n| --- | --- |\n| 3 | 4 |\n" ) path = _render(md, tmp_path) sheets = _read_workbook(path) assert "Table1" in sheets assert "Table2" in sheets assert sheets["Table1"][0] == ["A", "B"] assert sheets["Table2"][0] == ["C", "D"] def test_markdown_no_table_creates_summary(tmp_path: Path) -> None: """Markdown without tables puts text lines in a Summary sheet.""" md = "Just some text.\nAnother line.\n" path = _render(md, tmp_path) sheets = _read_workbook(path) # At least one sheet exists with the text all_text = [] for rows in sheets.values(): all_text.extend(cell for row in rows for cell in row) assert "Just some text." in all_text assert "Another line." in all_text def test_json_input_multi_sheet(tmp_path: Path) -> None: """JSON input {sheet: rows} creates named sheets.""" data = { "Sales": [["Product", "Revenue"], ["Widget", "1000"], ["Gadget", "2000"]], "Costs": [["Item", "Amount"], ["Rent", "500"]], } path = _render(json.dumps(data), tmp_path) sheets = _read_workbook(path) assert "Sales" in sheets assert "Costs" in sheets assert sheets["Sales"][0] == ["Product", "Revenue"] assert sheets["Sales"][1] == ["Widget", "1000"] assert sheets["Costs"][1] == ["Rent", "500"] def test_json_input_single_sheet(tmp_path: Path) -> None: """JSON with one sheet creates exactly that sheet.""" data = {"Data": [["X", "Y"], ["1", "2"]]} path = _render(json.dumps(data), tmp_path) sheets = _read_workbook(path) assert "Data" in sheets assert sheets["Data"][0] == ["X", "Y"] def test_empty_markdown(tmp_path: Path) -> None: """Empty input produces a valid workbook with at least one sheet.""" path = _render("", tmp_path) assert path.exists() wb = load_workbook(str(path)) assert len(wb.sheetnames) >= 1 def test_mixed_table_and_text(tmp_path: Path) -> None: """Text before/after a table goes to Summary, table goes to Table1.""" md = "Intro line.\n\n| Col1 | Col2 |\n| --- | --- |\n| a | b |\n\nOutro line.\n" path = _render(md, tmp_path) sheets = _read_workbook(path) assert "Table1" in sheets # Summary should contain intro and outro if "Summary" in sheets: summary_cells = [cell for row in sheets["Summary"] for cell in row] assert "Intro line." in summary_cells assert "Outro line." in summary_cells def test_long_sheet_name_truncated(tmp_path: Path) -> None: """Sheet names longer than 31 chars are truncated (Excel limit).""" long_name = "A" * 50 data = {long_name: [["x"]]} path = _render(json.dumps(data), tmp_path) wb = load_workbook(str(path)) # The sheet name should be at most 31 chars for name in wb.sheetnames: assert len(name) <= 31