125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
"""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
|