zapretgui/tests/test_preset_drag_indicator.py
loop-uh 018c57b924
All checks were successful
Zapret source guards / validate (push) Successful in 5s
Windows-сборка / Dev Dev 21.1.5.70 опубликован
Починить перемещение preset-ов и профилей
2026-08-19 23:37:37 +03:00

215 lines
9.5 KiB
Python

from __future__ import annotations
import inspect
import os
import unittest
from unittest.mock import Mock
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtCore import QPoint
from PyQt6.QtWidgets import QApplication
from ui.presets_menu import delegate as preset_delegate
from ui.presets_menu import common as preset_common
from ui.presets_menu.model import PresetListModel
from ui.presets_menu import view as preset_view
from presets.ui.common.user_presets_page import UserPresetsPageBase
class PresetDragIndicatorTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls._app = QApplication.instance() or QApplication([])
def test_drop_marker_maps_targets_to_clear_visual_modes(self) -> None:
self.assertEqual(
preset_view.preset_drop_marker_for_target(2, "folder"),
{"row": 2, "mode": "folder"},
)
self.assertEqual(
preset_view.preset_drop_marker_for_target(4, "preset"),
{"row": 4, "mode": "before"},
)
self.assertEqual(
preset_view.preset_drop_marker_for_target(-1, "empty"),
{"row": -1, "mode": ""},
)
def test_drop_target_uses_lower_half_as_after_row(self) -> None:
self.assertEqual(
preset_view.preset_drop_target_for_position(4, "preset", y=109, row_top=100, row_height=20),
{"marker": {"row": 4, "mode": "before"}, "destination_kind": "preset", "destination_row": 4},
)
self.assertEqual(
preset_view.preset_drop_target_for_position(4, "preset", y=112, row_top=100, row_height=20),
{"marker": {"row": 4, "mode": "after"}, "destination_kind": "preset_after", "destination_row": 4},
)
def test_adjacent_preset_gap_has_one_canonical_drop_target(self) -> None:
lower_half_target = preset_view.preset_drop_target_for_position(
4,
"preset",
y=112,
row_top=100,
row_height=20,
)
self.assertEqual(
preset_common.preset_canonical_drop_target_for_next_row(
lower_half_target,
next_row=5,
next_kind="preset",
),
{"marker": {"row": 5, "mode": "before"}, "destination_kind": "preset", "destination_row": 5},
)
def test_view_clears_drop_marker_when_drag_finishes_or_leaves(self) -> None:
view_source = inspect.getsource(preset_view.LinkedWheelListView)
self.assertIn("set_drop_marker", view_source)
self.assertIn("dragLeaveEvent", view_source)
self.assertIn("self.set_drop_marker(-1, \"\")", view_source)
def test_internal_drag_does_not_depend_on_windows_ole_drop_target(self) -> None:
view_source = inspect.getsource(preset_view.LinkedWheelListView.mouseMoveEvent)
finish_source = inspect.getsource(preset_view.LinkedWheelListView._finish_internal_drag)
self.assertIn("self._drag_source = (kind, source_id)", view_source)
self.assertIn("self._update_internal_drag", view_source)
self.assertNotIn("QDrag", view_source)
self.assertNotIn("restore_windows_qt_file_drop", view_source)
self.assertIn("self.item_dropped.emit", finish_source)
def test_internal_mouse_drag_emits_the_resolved_preset_move(self) -> None:
view = preset_view.LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.resize(500, 300)
view._drag_source = ("preset", "A.txt")
view._drop_target_at = Mock(
return_value=(
{"marker": {"row": 2, "mode": "before"}, "destination_kind": "preset"},
"B.txt",
"games",
)
)
dropped: list[tuple[str, str, str, str, str]] = []
view.item_dropped.connect(lambda *args: dropped.append(tuple(args)))
self.assertTrue(view._finish_internal_drag(QPoint(20, 20)))
self.assertEqual(dropped, [("preset", "A.txt", "preset", "B.txt", "games")])
self.assertIsNone(view._drag_source)
def test_view_updates_only_drop_marker_rows(self) -> None:
payload_source = inspect.getsource(preset_view.LinkedWheelListView.set_drop_marker_payload)
update_source = inspect.getsource(preset_view.LinkedWheelListView._update_drop_marker_rows)
self.assertIn("_update_drop_marker_rows", payload_source)
self.assertNotIn("viewport().update()", payload_source)
self.assertIn("viewport().update(rect", update_source)
def test_preset_current_index_helper_skips_already_selected_index(self) -> None:
class _Index:
def __init__(self, row: int) -> None:
self.row = row
def __eq__(self, other) -> bool:
return isinstance(other, _Index) and self.row == other.row
index = _Index(3)
view = Mock()
view.currentIndex.return_value = index
self.assertFalse(preset_common.set_current_index_if_changed(view, index))
view.setCurrentIndex.assert_not_called()
def test_view_sends_destination_folder_with_drop(self) -> None:
view_source = inspect.getsource(preset_view.LinkedWheelListView)
self.assertIn("item_dropped = pyqtSignal(str, str, str, str, str)", view_source)
self.assertIn("PresetListModel.FolderKeyRole", view_source)
self.assertIn("destination_folder_key", view_source)
self.assertIn(
"self.item_dropped.emit(source_kind, source_id, destination_kind, destination_id, destination_folder_key)",
view_source,
)
def test_delegate_draws_folder_and_before_row_drop_markers(self) -> None:
delegate_source = inspect.getsource(preset_delegate.PresetListDelegate)
self.assertIn("_paint_drop_marker", delegate_source)
self.assertIn('marker.get("mode") == "folder"', delegate_source)
self.assertIn('marker.get("mode") == "before"', delegate_source)
self.assertIn('marker.get("mode") == "after"', delegate_source)
self.assertIn("drag_marker_visible", delegate_source)
self.assertIn("active=is_active and not drag_marker_visible", delegate_source)
self.assertIn("show_active_marker=False", delegate_source)
def test_delegate_action_updates_only_affected_preset_row(self) -> None:
click_source = inspect.getsource(preset_delegate.PresetListDelegate._handle_action_click)
clear_source = inspect.getsource(preset_delegate.PresetListDelegate._clear_pending_destructive)
shake_source = inspect.getsource(preset_delegate.PresetListDelegate._advance_pending_shake)
self.assertIn("_update_preset_row_view", click_source)
self.assertIn("_update_pending_destructive_row", clear_source)
self.assertIn("_update_pending_destructive_row", shake_source)
self.assertNotIn("viewport().update()", click_source)
self.assertNotIn("viewport().update()", clear_source)
self.assertNotIn("viewport().update()", shake_source)
def test_model_moves_preset_row_without_full_reload(self) -> None:
model = PresetListModel()
model.set_rows(
[
{"kind": "folder", "folder_key": "common", "name": "Общие", "count": 1, "is_collapsed": False},
{"kind": "preset", "file_name": "A.txt", "name": "A", "folder_key": "common"},
{"kind": "folder", "folder_key": "game-filter", "name": "Game filter", "count": 1, "is_collapsed": False},
{"kind": "preset", "file_name": "B.txt", "name": "B", "folder_key": "game-filter"},
]
)
model.beginResetModel = Mock(side_effect=AssertionError("move must not reset the whole preset list"))
self.assertTrue(model.move_preset("A.txt", "preset_after", "B.txt", "game-filter"))
rows = [
(
model.index(row, 0).data(PresetListModel.KindRole),
model.index(row, 0).data(PresetListModel.FolderKeyRole),
model.index(row, 0).data(PresetListModel.FileNameRole),
model.index(row, 0).data(PresetListModel.CountRole),
)
for row in range(model.rowCount())
]
self.assertIn(("preset", "game-filter", "A.txt", 0), rows)
self.assertNotIn(("preset", "common", "A.txt", 0), rows)
self.assertIn(("folder", "common", "", 0), rows)
self.assertIn(("folder", "game-filter", "", 2), rows)
def test_model_keeps_folder_count_when_preset_moves_inside_same_folder(self) -> None:
model = PresetListModel()
model.set_rows(
[
{"kind": "folder", "folder_key": "common", "name": "Общие", "count": 2, "is_collapsed": False},
{"kind": "preset", "file_name": "A.txt", "name": "A", "folder_key": "common"},
{"kind": "preset", "file_name": "B.txt", "name": "B", "folder_key": "common"},
]
)
model.beginResetModel = Mock(side_effect=AssertionError("move must not reset the whole preset list"))
self.assertTrue(model.move_preset("A.txt", "preset_after", "B.txt", "common"))
self.assertEqual(model.index(0, 0).data(PresetListModel.CountRole), 2)
def test_local_preset_move_preserves_scroll_position(self) -> None:
move_source = inspect.getsource(UserPresetsPageBase._apply_preset_move_locally)
self.assertIn("capture_presets_view_state", move_source)
self.assertIn("restore_presets_view_state", move_source)
self.assertNotIn("ensure_preset_list_current_index", move_source)
if __name__ == "__main__":
unittest.main()