"""Round 18: the 2026-07-02 "New backlog" batch. 1. Visualizer: gray-mode color slider pref + mode persists across launches. 2. External file drops insert at the drop position in a playlist. 3. Renaming artist/album/album_artist moves the file to keep the music folder organized (Artist/Album, Unknown Artist fallback). """ from PyQt6.QtCore import QObject, pyqtSignal from PyQt6.QtGui import QColor from lintunes import theme from lintunes.preferences import Preferences from lintunes.gui.visualizer import ( VisualizerWidget, MODE_ON, MODE_DIM, MODE_OFF) class _FakePlayer(QObject): audio_buffer = pyqtSignal(object) playing_changed = pyqtSignal(bool) track_changed = pyqtSignal(object) def __init__(self): super().__init__() self._playing = False def is_playing(self): return self._playing # -------------------------------------------------------------------------- # 1. Visualizer: gray color pref + persistent mode # -------------------------------------------------------------------------- class TestVisualizerModePersistence: def test_no_prefs_defaults_on(self, qapp): vis = VisualizerWidget(_FakePlayer()) assert vis._mode == MODE_ON def test_saved_mode_restored(self, qapp, tmp_path): prefs = Preferences(tmp_path) prefs.set("visualizer_mode", MODE_DIM) vis = VisualizerWidget(_FakePlayer(), prefs) assert vis._mode == MODE_DIM def test_saved_off_restored_and_stays_idle(self, qapp, tmp_path): prefs = Preferences(tmp_path) prefs.set("visualizer_mode", MODE_OFF) vis = VisualizerWidget(_FakePlayer(), prefs) assert vis._mode == MODE_OFF vis._on_playing_changed(True) # playback starts assert not vis._timer.isActive() def test_bogus_saved_mode_falls_back_to_on(self, qapp, tmp_path): prefs = Preferences(tmp_path) prefs.set("visualizer_mode", "sparkle") # hand-edited prefs file vis = VisualizerWidget(_FakePlayer(), prefs) assert vis._mode == MODE_ON def test_click_persists_mode_to_disk(self, qapp, tmp_path): prefs = Preferences(tmp_path) vis = VisualizerWidget(_FakePlayer(), prefs) vis.mousePressEvent(None) # on -> dim assert prefs.get("visualizer_mode") == MODE_DIM # A fresh Preferences (= next launch) sees it too. assert Preferences(tmp_path).get("visualizer_mode") == MODE_DIM class TestVisualizerGrayColor: def test_default_gray_resolves_for_visualizer_key(self, qapp): value = theme.default_gray("color_visualizer_gray") assert isinstance(value, int) assert 0 <= value <= 255 def test_slider_pref_drives_dim_color(self, qapp, tmp_path): prefs = Preferences(tmp_path) prefs.set("color_visualizer_gray", 100) vis = VisualizerWidget(_FakePlayer(), prefs) vis._mode = MODE_DIM assert vis._bar_color() == QColor(100, 100, 100) def test_unset_pref_keeps_derived_color(self, qapp, tmp_path): prefs = Preferences(tmp_path) with_prefs = VisualizerWidget(_FakePlayer(), prefs) without = VisualizerWidget(_FakePlayer()) with_prefs._mode = MODE_DIM without._mode = MODE_DIM assert with_prefs._bar_color() == without._bar_color() def test_on_mode_ignores_gray_pref(self, qapp, tmp_path): prefs = Preferences(tmp_path) prefs.set("color_visualizer_gray", 100) vis = VisualizerWidget(_FakePlayer(), prefs) assert vis._mode == MODE_ON assert vis._bar_color() == QColor(vis.palette().highlight().color()) def test_visualizer_row_in_preferences_dialog(self, qapp): from lintunes.gui.preferences_dialog import _COLOR_ROWS assert "color_visualizer_gray" in [key for key, _label in _COLOR_ROWS] # -------------------------------------------------------------------------- # 2. External file drops insert at the drop position # -------------------------------------------------------------------------- def _seeded_manager(tmp_path, track_ids=(1, 2, 3)): from lintunes.models import Library, Track from lintunes.library_manager import LibraryManager tracks = {tid: Track(track_id=tid, name=f"T{tid}") for tid in track_ids} return LibraryManager(Library(tracks=tracks), tmp_path / "data") class TestFileDropPosition: def test_import_paths_inserts_at_position(self, qapp, tmp_path, mp3_file): from lintunes.importers import file_importer manager = _seeded_manager(tmp_path) playlist = manager.create_playlist("Drops") manager.add_tracks_to_playlist(playlist.persistent_id, [1, 2, 3]) imported = file_importer.import_paths( [mp3_file], tmp_path / "music", manager, playlist.persistent_id, position=1) assert len(imported) == 1 new_id = imported[0].track_id assert playlist.track_ids == [1, new_id, 2, 3] def test_import_paths_default_appends(self, qapp, tmp_path, mp3_file): from lintunes.importers import file_importer manager = _seeded_manager(tmp_path) playlist = manager.create_playlist("Drops") manager.add_tracks_to_playlist(playlist.persistent_id, [1, 2]) imported = file_importer.import_paths( [mp3_file], tmp_path / "music", manager, playlist.persistent_id) assert playlist.track_ids == [1, 2, imported[0].track_id] def test_duplicate_file_inserts_without_new_track(self, qapp, tmp_path, mp3_file): from lintunes.importers import file_importer manager = _seeded_manager(tmp_path) existing = file_importer.import_file( mp3_file, tmp_path / "music", manager) playlist = manager.create_playlist("Drops") manager.add_tracks_to_playlist(playlist.persistent_id, [1, 2]) count_before = len(manager.library.tracks) imported = file_importer.import_paths( [mp3_file], tmp_path / "music", manager, playlist.persistent_id, position=0) assert imported[0].track_id == existing.track_id assert playlist.track_ids == [existing.track_id, 1, 2] assert len(manager.library.tracks) == count_before def test_playlist_view_relays_drop_row(self, qapp, tmp_path): from lintunes.gui.playlist_view import PlaylistView manager = _seeded_manager(tmp_path) playlist = manager.create_playlist("Drops") view = PlaylistView(manager) view.show_playlist(playlist) received = [] view.files_dropped.connect( lambda paths, pid, row: received.append((paths, pid, row))) view._on_files_dropped(["/x.mp3"], 2) view._on_files_dropped(["/y.mp3"], None) assert received == [(["/x.mp3"], playlist.persistent_id, 2), (["/y.mp3"], playlist.persistent_id, None)]