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