Files
lintunes/tests/test_round25.py

136 lines
5.8 KiB
Python

"""Round 25: folder highlight while dragging a playlist.
Hovering a folder mid-drag lights it up so the user can see the drop would
land inside it (previously there was no indication at all). A folder in the
dragged row's own branch is never highlighted — the manager rejects that drop
as a cycle, so lighting it up would lie. The autoscroll tick's highlight
refresh is now kind-aware so a folder highlight survives edge auto-scrolling.
Synthetic QDropEvents report source() == None, so the internal-move branch of
dragMoveEvent can't be driven directly; _highlight_drop_target carries the
logic and is what gets tested.
"""
from PyQt6.QtCore import QPoint
from lintunes.models import Library, Track
from lintunes.library_manager import LibraryManager
from lintunes.gui.sidebar import PlaylistTree
def _tree(qapp, tmp_path, height=400):
library = Library(tracks={1: Track(track_id=1, name="One")})
manager = LibraryManager(library, tmp_path)
tree = PlaylistTree(manager)
tree.resize(200, height)
tree.show()
return manager, tree
def _pos_over(tree, item) -> QPoint:
return tree.visualItemRect(item).center()
class TestFolderHighlight:
def test_folder_highlighted_while_dragging_playlist(self, qapp, tmp_path):
manager, tree = _tree(qapp, tmp_path)
folder = manager.create_folder("a folder")
playlist = manager.create_playlist("a playlist")
folder_item = tree._find_item(folder.persistent_id)
tree.setCurrentItem(tree._find_item(playlist.persistent_id))
tree._highlight_drop_target(_pos_over(tree, folder_item), "folder")
assert tree._drop_target is folder_item
def test_leaving_the_folder_clears_the_highlight(self, qapp, tmp_path):
manager, tree = _tree(qapp, tmp_path)
folder = manager.create_folder("a folder")
playlist = manager.create_playlist("a playlist")
folder_item = tree._find_item(folder.persistent_id)
tree.setCurrentItem(tree._find_item(playlist.persistent_id))
tree._highlight_drop_target(_pos_over(tree, folder_item), "folder")
assert tree._drop_target is not None
# Hover empty space below the rows (drop there = top level).
tree._highlight_drop_target(
QPoint(10, tree.viewport().height() - 2), "folder")
assert tree._drop_target is None
def test_playlist_row_is_not_a_folder_target(self, qapp, tmp_path):
manager, tree = _tree(qapp, tmp_path)
playlist = manager.create_playlist("a playlist")
other = manager.create_playlist("another playlist")
tree.setCurrentItem(tree._find_item(other.persistent_id))
tree._highlight_drop_target(
_pos_over(tree, tree._find_item(playlist.persistent_id)), "folder")
assert tree._drop_target is None
def test_dragged_folder_cannot_drop_into_its_own_branch(
self, qapp, tmp_path):
manager, tree = _tree(qapp, tmp_path)
outer = manager.create_folder("a outer")
inner = manager.create_folder("b inner", outer.persistent_id)
other = manager.create_folder("c other")
outer_item = tree._find_item(outer.persistent_id)
inner_item = tree._find_item(inner.persistent_id)
other_item = tree._find_item(other.persistent_id)
outer_item.setExpanded(True)
tree.setCurrentItem(outer_item) # dragging "outer"
# Onto itself: no highlight (manager would no-op the drop).
tree._highlight_drop_target(_pos_over(tree, outer_item), "folder")
assert tree._drop_target is None
# Into its own subfolder: no highlight (manager rejects the cycle).
tree._highlight_drop_target(_pos_over(tree, inner_item), "folder")
assert tree._drop_target is None
# Into an unrelated folder: highlighted.
tree._highlight_drop_target(_pos_over(tree, other_item), "folder")
assert tree._drop_target is other_item
def test_track_drag_still_highlights_playlists(self, qapp, tmp_path):
manager, tree = _tree(qapp, tmp_path)
folder = manager.create_folder("a folder")
playlist = manager.create_playlist("a playlist")
folder_item = tree._find_item(folder.persistent_id)
playlist_item = tree._find_item(playlist.persistent_id)
tree._highlight_drop_target(_pos_over(tree, playlist_item), "playlist")
assert tree._drop_target is playlist_item
# Folders are not track-drop targets.
tree._highlight_drop_target(_pos_over(tree, folder_item), "playlist")
assert tree._drop_target is None
class TestKindAwareTick:
def test_tick_clears_folder_highlight_scrolled_out_from_under_cursor(
self, qapp, tmp_path):
manager, tree = _tree(qapp, tmp_path, height=100)
folder = manager.create_folder("aaa folder") # sorts to the top row
dragged = None
for i in range(60):
dragged = manager.create_playlist(f"playlist {i}")
tree.setCurrentItem(tree._find_item(dragged.persistent_id))
tree.scrollToTop() # setCurrentItem scrolled the dragged row into view
pos = _pos_over(tree, tree._find_item(folder.persistent_id))
tree._highlight_drop_target(pos, "folder")
assert tree._drop_target is not None
# Cursor held still over that spot while the edge auto-scroll rolls:
# the folder scrolls away and a non-target row takes its place.
tree._drag_target_kind = "folder"
tree._autoscroll_pos = pos
tree._autoscroll_dir = 1
assert tree.verticalScrollBar().value() == 0
tree._autoscroll_tick()
assert tree.verticalScrollBar().value() == 1
assert tree._drop_target is None
def test_stop_autoscroll_resets_drag_target_kind(self, qapp, tmp_path):
_manager, tree = _tree(qapp, tmp_path)
tree._drag_target_kind = "folder"
tree._stop_autoscroll()
assert tree._drag_target_kind is None