All checks were successful
Zapret source guards / validate (push) Successful in 9s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
163 lines
7.3 KiB
Python
163 lines
7.3 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from PyQt6.QtCore import QPoint
|
|
|
|
from ui.widgets.tile_layout import (
|
|
TILE_ROW_HEADER,
|
|
TILE_ROW_ITEM,
|
|
TILE_ROW_WIDE,
|
|
TileMetrics,
|
|
build_tile_geometry,
|
|
tile_column_count,
|
|
)
|
|
|
|
|
|
METRICS = TileMetrics(min_column_width=460, max_columns=3, gap=12, card_padding_top=4, card_padding_bottom=6)
|
|
|
|
|
|
def _rows(*group_sizes: int) -> tuple[list[str], list[int]]:
|
|
"""Группы «заголовок + N строк»: заголовок высотой 36, строка 32."""
|
|
kinds: list[str] = []
|
|
heights: list[int] = []
|
|
for size in group_sizes:
|
|
kinds.append(TILE_ROW_HEADER)
|
|
heights.append(36)
|
|
kinds.extend([TILE_ROW_ITEM] * size)
|
|
heights.extend([32] * size)
|
|
return kinds, heights
|
|
|
|
|
|
class TileColumnCountTests(unittest.TestCase):
|
|
def test_column_count_follows_width_and_is_capped(self) -> None:
|
|
self.assertEqual(tile_column_count(300, METRICS), 1)
|
|
self.assertEqual(tile_column_count(931, METRICS), 1)
|
|
# Два столбца по 460 и промежуток 12 помещаются ровно в 932.
|
|
self.assertEqual(tile_column_count(932, METRICS), 2)
|
|
self.assertEqual(tile_column_count(1150, METRICS), 2)
|
|
self.assertEqual(tile_column_count(1404, METRICS), 3)
|
|
self.assertEqual(tile_column_count(4000, METRICS), 3)
|
|
|
|
|
|
class TileGeometryTests(unittest.TestCase):
|
|
def test_single_column_stacks_cards_with_gap(self) -> None:
|
|
kinds, heights = _rows(2, 1)
|
|
geometry = build_tile_geometry(kinds, heights, 600, METRICS)
|
|
|
|
self.assertEqual(geometry.column_count, 1)
|
|
self.assertEqual(len(geometry.cards), 2)
|
|
first, second = geometry.cards
|
|
self.assertEqual(first.rect.top(), 0)
|
|
self.assertEqual(first.rect.height(), 4 + 36 + 32 * 2 + 6)
|
|
self.assertEqual(first.rect.width(), 600)
|
|
self.assertEqual(second.rect.top(), first.rect.bottom() + 1 + 12)
|
|
self.assertEqual(geometry.content_height, second.rect.bottom() + 1)
|
|
|
|
def test_rows_are_stacked_inside_their_card(self) -> None:
|
|
kinds, heights = _rows(2)
|
|
geometry = build_tile_geometry(kinds, heights, 600, METRICS)
|
|
|
|
header, first_row, second_row = geometry.row_rects
|
|
self.assertEqual(header.top(), 4)
|
|
self.assertEqual(header.height(), 36)
|
|
self.assertEqual(first_row.top(), header.bottom() + 1)
|
|
self.assertEqual(second_row.top(), first_row.bottom() + 1)
|
|
self.assertEqual(geometry.row_cards, (0, 0, 0))
|
|
self.assertTrue(geometry.cards[0].rect.contains(second_row))
|
|
|
|
def test_next_card_goes_to_the_shortest_column(self) -> None:
|
|
# Высокая плитка слева; низкие уходят направо одна под другую, пока
|
|
# правый столбец остаётся короче левого.
|
|
kinds, heights = _rows(6, 1, 1, 1)
|
|
geometry = build_tile_geometry(kinds, heights, 1000, METRICS)
|
|
|
|
self.assertEqual(geometry.column_count, 2)
|
|
self.assertEqual([card.column for card in geometry.cards], [0, 1, 1, 1])
|
|
column_width = geometry.cards[0].rect.width()
|
|
self.assertEqual(column_width, (1000 - 12) // 2)
|
|
self.assertEqual(geometry.cards[1].rect.left(), column_width + 12)
|
|
self.assertEqual(geometry.cards[1].rect.top(), 0)
|
|
self.assertEqual(geometry.cards[2].rect.top(), geometry.cards[1].rect.bottom() + 1 + 12)
|
|
|
|
for index, card in enumerate(geometry.cards):
|
|
for other in geometry.cards[index + 1 :]:
|
|
self.assertFalse(card.rect.intersects(other.rect))
|
|
|
|
def test_equal_columns_prefer_the_left_one(self) -> None:
|
|
kinds, heights = _rows(1, 1, 1)
|
|
geometry = build_tile_geometry(kinds, heights, 1000, METRICS)
|
|
|
|
self.assertEqual([card.column for card in geometry.cards], [0, 1, 0])
|
|
|
|
def test_wide_row_spans_all_columns_and_restarts_them(self) -> None:
|
|
kinds, heights = _rows(3, 1)
|
|
kinds.append(TILE_ROW_WIDE)
|
|
heights.append(64)
|
|
tail_kinds, tail_heights = _rows(1)
|
|
geometry = build_tile_geometry(kinds + tail_kinds, heights + tail_heights, 1000, METRICS)
|
|
|
|
wide_row = len(kinds) - 1
|
|
wide_rect = geometry.row_rects[wide_row]
|
|
tallest_bottom = max(card.rect.bottom() for card in geometry.cards[:2])
|
|
self.assertEqual(wide_rect.left(), 0)
|
|
self.assertEqual(wide_rect.width(), 1000)
|
|
self.assertEqual(wide_rect.top(), tallest_bottom + 1 + 12)
|
|
self.assertEqual(geometry.row_cards[wide_row], -1)
|
|
self.assertEqual(geometry.cards[2].column, 0)
|
|
self.assertEqual(geometry.cards[2].rect.top(), wide_rect.bottom() + 1 + 12)
|
|
|
|
def test_item_without_header_still_gets_a_card(self) -> None:
|
|
geometry = build_tile_geometry([TILE_ROW_ITEM, TILE_ROW_ITEM], [32, 32], 600, METRICS)
|
|
|
|
self.assertEqual(len(geometry.cards), 1)
|
|
self.assertEqual((geometry.cards[0].first_row, geometry.cards[0].last_row), (0, 1))
|
|
|
|
def test_empty_model_has_no_content(self) -> None:
|
|
geometry = build_tile_geometry([], [], 600, METRICS)
|
|
|
|
self.assertEqual(geometry.content_height, 0)
|
|
self.assertEqual(geometry.cards, ())
|
|
self.assertEqual(geometry.row_at(QPoint(10, 10)), -1)
|
|
|
|
|
|
class TileGeometryLookupTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
kinds, heights = _rows(3, 2)
|
|
self.geometry = build_tile_geometry(kinds, heights, 1000, METRICS)
|
|
|
|
def test_row_at_finds_row_in_either_column(self) -> None:
|
|
for row, rect in enumerate(self.geometry.row_rects):
|
|
self.assertEqual(self.geometry.row_at(rect.center()), row)
|
|
# Нижнее поле плитки строкой не считается.
|
|
card = self.geometry.cards[0]
|
|
self.assertEqual(self.geometry.row_at(QPoint(card.rect.left() + 20, card.rect.bottom() - 1)), -1)
|
|
|
|
def test_card_padding_resolves_to_nearest_row_of_that_card(self) -> None:
|
|
card = self.geometry.cards[1]
|
|
below_last_row = QPoint(card.rect.center().x(), card.rect.bottom() - 1)
|
|
|
|
card_index = self.geometry.card_at(below_last_row)
|
|
self.assertEqual(card_index, 1)
|
|
self.assertEqual(self.geometry.nearest_row_in_card(card_index, below_last_row.y()), card.last_row)
|
|
|
|
def test_gap_between_cards_needs_slack(self) -> None:
|
|
left, right = self.geometry.cards
|
|
in_gap = QPoint(left.rect.right() + 3, 20)
|
|
|
|
self.assertEqual(self.geometry.card_at(in_gap), -1)
|
|
self.assertEqual(self.geometry.card_at(in_gap, slack=6), 0)
|
|
self.assertEqual(self.geometry.card_at(QPoint(right.rect.left() - 3, 20), slack=6), 1)
|
|
|
|
def test_neighbor_row_keeps_height_and_stops_at_edges(self) -> None:
|
|
# Вторая строка левой плитки стоит на высоте первой строки правой.
|
|
self.assertEqual(self.geometry.neighbor_row(1, 1), 5)
|
|
self.assertEqual(self.geometry.neighbor_row(5, -1), 1)
|
|
self.assertEqual(self.geometry.neighbor_row(1, -1), -1)
|
|
self.assertEqual(self.geometry.neighbor_row(5, 1), -1)
|
|
# Ниже правой плитки строк нет — берётся её последняя строка.
|
|
self.assertEqual(self.geometry.neighbor_row(3, 1), 6)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|