Files
schedule/tests/test_xlsx.py
Vladislav Kopnin e61ff1d683 xlsx: чистить «осиротевшие» значения в мержах (призрачные занятия)
В интерфейсе в объединённом блоке видно только верхне-левую ячейку;
значения, оставшиеся в остальных ячейках мержа (занесли текст, а сверху
наложили пустой мерж), в таблице не видны. Раньше парсер их отдавал:
реальный кейс — ПИЭ-41БО, вторник, 1 пара, знаменатель (мерж AH14:AI15).
По листу таких ячеек ровно одна; по 33 группам diff только этот слот.
2026-09-09 18:00:13 +03:00

102 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Тесты чтения 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