Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
102 lines
4.0 KiB
Python
102 lines
4.0 KiB
Python
"""Модель листа `Sheet`: merged-диапазоны, порядок ячеек строки, пустота, буквы колонок для диагностики."""
|
||
|
||
import datetime
|
||
from decimal import Decimal
|
||
|
||
import pytest
|
||
|
||
from src.parser.grid import EMPTY_CELL, Cell, MergedRange, Sheet, cell_ref, column_letter
|
||
|
||
|
||
def _sheet(
|
||
cells: dict[tuple[int, int], Cell],
|
||
merged: tuple[MergedRange, ...] = (),
|
||
row_outline: dict[int, int] | None = None,
|
||
) -> Sheet:
|
||
n_rows = max((row for row, _ in cells), default=0)
|
||
n_cols = max((col for _, col in cells), default=0)
|
||
return Sheet(name="ОСВ", cells=cells, merged=merged, row_outline=row_outline or {}, n_rows=n_rows, n_cols=n_cols)
|
||
|
||
|
||
def test_text_at_inside_merged_range_takes_origin_value() -> None:
|
||
sheet = _sheet(
|
||
{(1, 2): Cell("Сальдо на начало периода", "text"), (2, 2): Cell("Дебет", "text")},
|
||
merged=(MergedRange(top=1, left=2, bottom=1, right=3),),
|
||
)
|
||
|
||
assert sheet.text_at(1, 3) == "Сальдо на начало периода"
|
||
assert sheet.text_at(1, 2) == "Сальдо на начало периода"
|
||
assert sheet.cell(1, 3) is EMPTY_CELL
|
||
assert sheet.merged_origin(1, 3) == (1, 2)
|
||
assert sheet.merged_span(1, 3) == MergedRange(1, 2, 1, 3)
|
||
assert sheet.text_at(2, 3) == ""
|
||
assert sheet.merged_origin(2, 3) == (2, 3)
|
||
assert sheet.merged_span(2, 3) is None
|
||
|
||
|
||
def test_large_merged_range_is_resolved_without_cell_index() -> None:
|
||
# Диапазон на весь столбец: индекс «ячейка → диапазон» для него не строится, поиск всё равно работает
|
||
big = MergedRange(top=1, left=1, bottom=1_048_576, right=16_384)
|
||
sheet = _sheet({(1, 1): Cell("Шапка", "text")}, merged=(big,))
|
||
|
||
assert sheet.merged_origin(500_000, 10_000) == (1, 1)
|
||
assert sheet.text_at(1_048_576, 16_384) == "Шапка"
|
||
|
||
|
||
def test_row_cells_are_ordered_by_column_and_skip_blank_text() -> None:
|
||
sheet = _sheet(
|
||
{
|
||
(3, 5): Cell(Decimal("10.5"), "number"),
|
||
(3, 1): Cell("60.01", "text"),
|
||
(3, 3): Cell(" ", "text"),
|
||
(4, 1): Cell("62", "text"),
|
||
}
|
||
)
|
||
|
||
assert sheet.row_cells(3) == [(1, Cell("60.01", "text")), (5, Cell(Decimal("10.5"), "number"))]
|
||
assert sheet.row_cells(7) == []
|
||
|
||
|
||
def test_row_of_spaces_is_empty() -> None:
|
||
sheet = _sheet({(2, 1): Cell(" ", "text"), (2, 4): Cell("\xa0 ", "text"), (3, 1): Cell(False, "bool")})
|
||
|
||
assert sheet.is_row_empty(2)
|
||
assert sheet.is_row_empty(10)
|
||
assert not sheet.is_row_empty(3)
|
||
|
||
|
||
def test_cell_text_strips_and_treats_spaces_as_empty() -> None:
|
||
assert Cell(" ", "text").text == ""
|
||
assert Cell(" Контрагент ", "text").text == "Контрагент"
|
||
assert Cell(None, "empty").text == ""
|
||
assert EMPTY_CELL.text == ""
|
||
assert Cell(Decimal("1250.00"), "number").text == "1250.00"
|
||
assert Cell(datetime.date(2024, 9, 30), "date").text == "2024-09-30"
|
||
|
||
|
||
def test_outline_defaults_to_zero() -> None:
|
||
sheet = _sheet({(1, 1): Cell("62", "text")}, row_outline={2: 1, 3: 2})
|
||
|
||
assert [sheet.outline(row) for row in (1, 2, 3)] == [0, 1, 2]
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("index", "letter"),
|
||
[(1, "A"), (26, "Z"), (27, "AA"), (52, "AZ"), (703, "AAA"), (16_384, "XFD")],
|
||
)
|
||
def test_column_letter(index: int, letter: str) -> None:
|
||
assert column_letter(index) == letter
|
||
|
||
|
||
def test_column_letter_rejects_non_positive_index() -> None:
|
||
with pytest.raises(ValueError, match="1-based"):
|
||
column_letter(0)
|
||
|
||
|
||
def test_cell_ref_quotes_sheet_name_by_excel_rule() -> None:
|
||
assert cell_ref("Баланс", 27, 3) == "Баланс!C27"
|
||
assert cell_ref("Лист_1", 2, 2) == "Лист_1!B2"
|
||
assert cell_ref("Бухгалтерский баланс. Лист 1", 14, 3) == "'Бухгалтерский баланс. Лист 1'!C14"
|
||
assert cell_ref("2024", 1, 1) == "'2024'!A1"
|
||
assert cell_ref("Отчёт д'Артаньяна", 1, 28) == "'Отчёт д''Артаньяна'!AB1"
|