В интерфейсе в объединённом блоке видно только верхне-левую ячейку; значения, оставшиеся в остальных ячейках мержа (занесли текст, а сверху наложили пустой мерж), в таблице не видны. Раньше парсер их отдавал: реальный кейс — ПИЭ-41БО, вторник, 1 пара, знаменатель (мерж AH14:AI15). По листу таких ячеек ровно одна; по 33 группам diff только этот слот.
102 lines
3.7 KiB
Python
102 lines
3.7 KiB
Python
"""Тесты чтения xlsx: ячейки, мержи, скрытые столбцы, красный текст, даты."""
|
||
|
||
import zipfile
|
||
from datetime import date
|
||
|
||
import pytest
|
||
|
||
from fixtures import fixture_sheets, fixture_xlsx, MAIN
|
||
from schedule.errors import ScheduleError
|
||
from schedule.xlsx import excel_serial_to_date, parse_workbook
|
||
from xlsx_builder import SheetSpec, build_workbook
|
||
|
||
|
||
def test_merge_orphan_value_cleared():
|
||
"""Значение в не-топ ячейке мержа в интерфейсе не видно — парсер его не отдаёт.
|
||
|
||
Реальный кейс: в таблице занятие, а сверху на колонку группы наложили
|
||
пустой 2x2-мерж; в xlsx значение осталось, в таблице — пусто.
|
||
"""
|
||
spec = SheetSpec(
|
||
rows=[
|
||
["", "", "", ""],
|
||
["т", "", "", ""], # A2 — топ мержа A2:B3
|
||
["", "осиротевшее", "", "призрак"], # B3, D3
|
||
],
|
||
merges=["A2:B3", "D2:D3"],
|
||
)
|
||
sheets = parse_workbook(build_workbook({"s": spec}))
|
||
rows = sheets["s"].rows
|
||
# топ-значение разлилось по всему диапазону
|
||
assert rows[1][0] == rows[1][1] == rows[2][0] == rows[2][1] == "т"
|
||
# D2 пустое, D3 было «призрак» за мержом -> обе пусто
|
||
assert rows[1][3] == "" and rows[2][3] == ""
|
||
|
||
|
||
def test_parse_workbook_fixture():
|
||
sheets = parse_workbook(fixture_xlsx())
|
||
assert set(sheets) == {"Бакалавры и магистры", "аудитории", "Преподаватели"}
|
||
|
||
rows = sheets[MAIN].rows
|
||
# заголовок группы
|
||
assert rows[0][6] == "ИВТ-11БО\nсеместр до 11.01.27"
|
||
# скрытый столбец
|
||
assert 8 in sheets[MAIN].hidden_cols
|
||
# дата-серия как число
|
||
assert rows[2][0] == "46265"
|
||
# мерж F3:F4 -> значение пары попало во вторую строку
|
||
assert rows[3][5] == "1\n9:00-\n10:35"
|
||
assert rows[2][5] == "1\n9:00-\n10:35"
|
||
|
||
|
||
def test_red_runs_marked():
|
||
sheets = parse_workbook(fixture_xlsx())
|
||
cell = sheets[MAIN].rows[2][6]
|
||
assert cell.startswith("Математика\nСмирнов А.В.\nауд. 215\n")
|
||
assert "\u27EA" in cell and "\u27EB" in cell
|
||
assert cell.endswith("пара 14.09 не будет\u27EB")
|
||
|
||
|
||
def test_empty_cells_are_empty_strings():
|
||
sheets = parse_workbook(fixture_xlsx())
|
||
assert sheets[MAIN].rows[5][6] == ""
|
||
# все строки одной ширины
|
||
widths = {len(r) for r in sheets[MAIN].rows}
|
||
assert len(widths) == 1
|
||
|
||
|
||
def test_bad_zip_raises():
|
||
with pytest.raises(ScheduleError):
|
||
parse_workbook(b"this is not an xlsx file")
|
||
|
||
|
||
def test_truncated_zip_raises():
|
||
good = fixture_xlsx()
|
||
with pytest.raises(ScheduleError):
|
||
parse_workbook(good[:20])
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"serial, expected",
|
||
[
|
||
("46265", date(2026, 8, 31)),
|
||
(46265, date(2026, 8, 31)),
|
||
("46278", date(2026, 9, 13)),
|
||
# до 1900: Excel считает несуществующий 29.02.1900
|
||
(1, date(1900, 1, 1)),
|
||
(59, date(1900, 2, 28)),
|
||
(61, date(1900, 3, 1)),
|
||
],
|
||
)
|
||
def test_excel_serial_to_date(serial, expected):
|
||
assert excel_serial_to_date(serial) == expected
|
||
|
||
|
||
@pytest.mark.parametrize("bad", ["", "текст", None, "0", "-5", "1,5x"])
|
||
def test_excel_serial_to_date_invalid(bad):
|
||
# "1,5x" не парсится; остальные — None
|
||
if bad == "1,5x":
|
||
assert excel_serial_to_date(bad) is None
|
||
else:
|
||
assert excel_serial_to_date(bad) is None
|