A15 — le rendu tableur lit les metadonnees du classeur (une charge en mode
normal par feuille) et les restitue : styles inline data-driven (couleur de
police en premier, fond, gras/italique, format numerique signale en mono),
alignements non-defaut, plages fusionnees (rowSpan/colSpan cote client) et
ancrage des volets figes (sticky rows/cols). Le chargement lazy replie les
metadonnees de chaque fenetre. Contrat API : styles/aligns = {ref: css},
merges = [ranges], freeze = ancre. 9 tests backend + contre-preuve (5 echecs
sur neutralisation), JSDOM 33/33.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
100 lines
3.5 KiB
Python
100 lines
3.5 KiB
Python
"""Style metadata in the .xlsx viewer (#153 A15): bold, colors, number formats,
|
|
merged ranges and freeze panes — plus the endpoint wiring."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
openpyxl = pytest.importorskip("openpyxl")
|
|
|
|
VAULT = "TestVault"
|
|
|
|
|
|
@pytest.fixture
|
|
def styled_book(test_vault_dir: str) -> str:
|
|
"""A workbook with a bold red-font header, a filled cell, a merge and a freeze."""
|
|
from openpyxl.styles import Font, PatternFill
|
|
|
|
path = Path(test_vault_dir) / "styles.xlsx"
|
|
wb = openpyxl.Workbook()
|
|
ws = wb.active
|
|
ws.title = "Data"
|
|
ws["A1"] = "En-tête"
|
|
ws["A1"].font = Font(bold=True, italic=True, color="FFCC0000")
|
|
ws["B1"] = "Total"
|
|
ws["B1"].fill = PatternFill("solid", fgColor="FFFFEE99")
|
|
ws["B2"] = 1234.5
|
|
ws["B2"].number_format = "#,##0.00"
|
|
ws["C3"] = "fusionnée"
|
|
ws.merge_cells("C3:D3")
|
|
ws["C3"].value = "fusionnée"
|
|
ws.freeze_panes = "A2"
|
|
wb.save(path)
|
|
return str(path)
|
|
|
|
|
|
class TestStyleReading:
|
|
def test_bold_italic_and_font_color(self, styled_book):
|
|
from backend.xlsx_reader import read_sheet_styles
|
|
|
|
styles = read_sheet_styles(Path(styled_book), "Data")
|
|
a1 = styles["A1"]
|
|
assert "font-weight:600" in a1["style"]
|
|
assert "font-style:italic" in a1["style"]
|
|
assert a1["style"].startswith("color:#cc0000")
|
|
|
|
def test_background_fill_is_read(self, styled_book):
|
|
from backend.xlsx_reader import read_sheet_styles
|
|
|
|
styles = read_sheet_styles(Path(styled_book), "Data")
|
|
assert "background:#ffee99" in styles["B1"]["style"]
|
|
|
|
def test_number_format_is_signalled(self, styled_book):
|
|
from backend.xlsx_reader import read_sheet_styles
|
|
|
|
styles = read_sheet_styles(Path(styled_book), "Data")
|
|
assert "font-family" in styles["B2"]["style"]
|
|
|
|
def test_plain_cell_has_no_entry(self, styled_book):
|
|
from backend.xlsx_reader import read_sheet_styles
|
|
|
|
styles = read_sheet_styles(Path(styled_book), "Data")
|
|
assert "A2" not in styles # untouched cell
|
|
|
|
def test_merges_and_freeze(self, styled_book):
|
|
from backend.xlsx_reader import read_sheet_freeze, read_sheet_merges
|
|
|
|
assert read_sheet_merges(Path(styled_book), "Data") == ["C3:D3"]
|
|
assert read_sheet_freeze(Path(styled_book), "Data") == "A2"
|
|
|
|
def test_unknown_sheet_yields_empty(self, styled_book):
|
|
from backend.xlsx_reader import read_sheet_styles
|
|
|
|
assert read_sheet_styles(Path(styled_book), "Nope") == {}
|
|
|
|
def test_broken_file_yields_empty(self, test_vault_dir):
|
|
from backend.xlsx_reader import read_sheet_styles
|
|
|
|
bad = Path(test_vault_dir) / "broken-styles.xlsx"
|
|
bad.write_bytes(b"not a zip")
|
|
assert read_sheet_styles(bad, "Data") == {}
|
|
|
|
|
|
class TestEndpointWiring:
|
|
def test_read_response_carries_styles_merges_freeze(self, client, styled_book):
|
|
resp = client.get(f"/api/file/{VAULT}", params={"path": "styles.xlsx"})
|
|
assert resp.status_code == 200
|
|
sheet = resp.json()["xlsx_sheets"][0]
|
|
assert sheet["styles"]["A1"].startswith("color:#cc0000")
|
|
assert sheet["aligns"] == {} or isinstance(sheet["aligns"], dict)
|
|
assert sheet["merges"] == ["C3:D3"]
|
|
assert sheet["freeze"] == "A2"
|
|
|
|
def test_rendered_html_carries_the_inline_style(self, client, styled_book):
|
|
resp = client.get(f"/api/file/{VAULT}", params={"path": "styles.xlsx"})
|
|
html = resp.json()["xlsx_sheets"][0]["html"]
|
|
assert 'data-cell="A1" style="' in html
|
|
assert "font-weight:600" in html
|