"""Round 24: sidebar drag auto-scroll. Dragging a playlist (or tracks) near the top/bottom edge of the sidebar's playlist tree scrolls the tree so off-screen rows can be reached. Same root cause and fix as the track table's Round 13: our dragMoveEvent accepts the event itself, so Qt's built-in autoscroll never runs. The direction helper (autoscroll_direction) is shared with the table and already covered by test_round13. """ 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, playlists=0): library = Library(tracks={1: Track(track_id=1, name="One")}) manager = LibraryManager(library, tmp_path) tree = PlaylistTree(manager) for i in range(playlists): manager.create_playlist(f"playlist {i}") # Small viewport: enough rows that the scrollbar gets a real range (the # view re-derives the range itself, so a manual setRange doesn't stick; # show() forces the polish/layout pass that computes it). tree.resize(200, 100) tree.show() return tree class TestAutoscrollTick: def test_tick_scrolls_by_direction(self, qapp, tmp_path): tree = _tree(qapp, tmp_path, playlists=60) bar = tree.verticalScrollBar() assert bar.maximum() > 20 # real range from the rows bar.setValue(10) tree._autoscroll_dir = 1 tree._autoscroll_tick() assert bar.value() == 11 tree._autoscroll_dir = -1 tree._autoscroll_tick() assert bar.value() == 10 def test_tick_stops_at_the_end(self, qapp, tmp_path): tree = _tree(qapp, tmp_path, playlists=60) bar = tree.verticalScrollBar() bar.setValue(bar.maximum()) # already at the bottom tree._autoscroll_dir = 1 tree._autoscroll_timer.start() tree._autoscroll_tick() assert bar.value() == bar.maximum() assert not tree._autoscroll_timer.isActive() class TestUpdateAutoscroll: def test_edge_starts_timer(self, qapp, tmp_path): tree = _tree(qapp, tmp_path) tree.resize(200, 300) tree._update_autoscroll(QPoint(10, 5), accepted=True) assert tree._autoscroll_dir == -1 assert tree._autoscroll_timer.isActive() def test_rejected_drop_keeps_timer_off(self, qapp, tmp_path): tree = _tree(qapp, tmp_path) tree.resize(200, 300) tree._update_autoscroll(QPoint(10, 5), accepted=False) assert tree._autoscroll_dir == 0 assert not tree._autoscroll_timer.isActive() def test_stop_autoscroll_resets(self, qapp, tmp_path): tree = _tree(qapp, tmp_path) tree._autoscroll_dir = 1 tree._autoscroll_timer.start() tree._stop_autoscroll() assert tree._autoscroll_dir == 0 assert tree._autoscroll_pos is None assert not tree._autoscroll_timer.isActive()