The table already computed and emitted the drop row for external URL drops, but PlaylistView._on_files_dropped threw it away, so imported files always appended. Thread it through: playlist_view.files_dropped gains a row slot (None = append), MainWindow.import_files and file_importer.import_paths take an optional position and hand it to add_tracks_to_playlist, which already supported positional insert. Files already in the library dedup to the existing track and insert at the drop position without duplicating the library entry.
174 lines
6.8 KiB
Python
174 lines
6.8 KiB
Python
"""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)]
|