"""Round 12: cut (Ctrl-X) + paste-above-selection. - The clipboard MIME carries a `cut` flag; a cut from a real playlist is a *move* on paste, a copy (or a cut from the library) just adds. - Paste inserts *above the currently selected track*, not at the end. - A cross-playlist move is one undo step (move_tracks_between_playlists). """ from PyQt6.QtWidgets import QApplication from lintunes.models import Library, Track from lintunes.library_manager import LibraryManager from lintunes.gui.track_table import ( TrackTableView, make_tracks_mime, parse_tracks_mime) from lintunes.gui.playlist_view import PlaylistView def _manager(tmp_path, n=4): library = Library(tracks={i: Track(track_id=i, name=f"T{i}") for i in range(1, n + 1)}) return LibraryManager(library, tmp_path) def _select_source_row(table, source_row): """Select the visual row backing a given source-model row (sort-proof).""" proxy_index = table.proxy.mapFromSource(table.model_.index(source_row, 0)) table.selectRow(proxy_index.row()) # -------------------------------------------------------------------------- # MIME + table helpers # -------------------------------------------------------------------------- class TestMimeAndHelpers: def test_cut_flag_round_trips(self): payload = parse_tracks_mime( make_tracks_mime([1, 2], "PID", [0, 1], cut=True)) assert payload["cut"] is True assert payload["track_ids"] == [1, 2] assert payload["source_playlist"] == "PID" assert payload["rows"] == [0, 1] def test_copy_defaults_to_not_cut(self): payload = parse_tracks_mime(make_tracks_mime([1])) assert payload["cut"] is False def test_paste_anchor_row(self, qapp, tmp_path): manager = _manager(tmp_path) table = TrackTableView(playlist_mode=True) table.set_tracks([manager.library.tracks[i] for i in (1, 2, 3)]) assert table.paste_anchor_row() is None # nothing selected _select_source_row(table, 1) assert table.paste_anchor_row() == 1 def test_cut_selection_marks_clipboard(self, qapp, tmp_path): manager = _manager(tmp_path) table = TrackTableView(playlist_mode=True) table.set_source_playlist("PID") table.set_tracks([manager.library.tracks[i] for i in (1, 2, 3)]) _select_source_row(table, 0) table.cut_selection() payload = parse_tracks_mime(QApplication.clipboard().mimeData()) assert payload["cut"] is True assert payload["track_ids"] == [1] # -------------------------------------------------------------------------- # manager: single-undo cross-playlist move # -------------------------------------------------------------------------- class TestMoveBetweenPlaylists: def test_move_and_single_undo(self, tmp_path): manager = _manager(tmp_path) a = manager.create_playlist("A") b = manager.create_playlist("B") manager.add_tracks_to_playlist(a.persistent_id, [1, 2, 3]) manager.add_tracks_to_playlist(b.persistent_id, [4]) manager.move_tracks_between_playlists( a.persistent_id, [0, 1], b.persistent_id, 0) assert a.track_ids == [3] assert b.track_ids == [1, 2, 4] manager.undo_stack.undo() # one step reverts both playlists assert a.track_ids == [1, 2, 3] assert b.track_ids == [4] def test_same_playlist_is_a_noop(self, tmp_path): manager = _manager(tmp_path) a = manager.create_playlist("A") manager.add_tracks_to_playlist(a.persistent_id, [1, 2, 3]) manager.move_tracks_between_playlists( a.persistent_id, [0], a.persistent_id, 2) assert a.track_ids == [1, 2, 3] # -------------------------------------------------------------------------- # PlaylistView paste integration # -------------------------------------------------------------------------- class TestPasteIntegration: def test_copy_paste_inserts_above_selection(self, qapp, tmp_path): manager = _manager(tmp_path) b = manager.create_playlist("B") manager.add_tracks_to_playlist(b.persistent_id, [4]) view = PlaylistView(manager) view.show_playlist(b) QApplication.clipboard().setMimeData(make_tracks_mime([1, 2])) _select_source_row(view.table, 0) # above track 4 view._on_paste() assert b.track_ids == [1, 2, 4] def test_cut_paste_moves_across_playlists(self, qapp, tmp_path): manager = _manager(tmp_path) a = manager.create_playlist("A") b = manager.create_playlist("B") manager.add_tracks_to_playlist(a.persistent_id, [1, 2, 3]) manager.add_tracks_to_playlist(b.persistent_id, [4]) view = PlaylistView(manager) view.show_playlist(b) QApplication.clipboard().setMimeData( make_tracks_mime([1, 2], a.persistent_id, [0, 1], cut=True)) _select_source_row(view.table, 0) # above track 4 view._on_paste() assert b.track_ids == [1, 2, 4] assert a.track_ids == [3] # The move consumes the clipboard so it can't be pasted again. assert parse_tracks_mime(QApplication.clipboard().mimeData()) is None def test_cut_paste_same_playlist_reorders(self, qapp, tmp_path): manager = _manager(tmp_path) a = manager.create_playlist("A") manager.add_tracks_to_playlist(a.persistent_id, [1, 2, 3, 4]) view = PlaylistView(manager) view.show_playlist(a) QApplication.clipboard().setMimeData( make_tracks_mime([1], a.persistent_id, [0], cut=True)) _select_source_row(view.table, 2) # above track 3 view._on_paste() assert a.track_ids == [2, 1, 3, 4]