import pathlib import shutil import pytest from solo_tool.solo_tool import SoloTool from player_mock import Player as MockPlayer @pytest.fixture def mockPlayer(): return MockPlayer() @pytest.fixture def uut(mockPlayer): return SoloTool(mockPlayer) @pytest.fixture def prepared_tmp_path(tmp_path): testFiles = [ "test.flac", "test.mp3", "test_session.json" ] for f in testFiles: shutil.copy(pathlib.Path(f), tmp_path) return tmp_path def test_playerControls(uut, mockPlayer): assert mockPlayer.state == MockPlayer.STOPPED assert uut.isPlaying() == False uut.play() assert mockPlayer.state == MockPlayer.PLAYING assert uut.isPlaying() == True uut.pause() assert mockPlayer.state == MockPlayer.PAUSED assert uut.isPlaying() == False uut.stop() assert mockPlayer.state == MockPlayer.STOPPED assert uut.isPlaying() == False assert mockPlayer.rate == 1.0 uut.rate = 0.5 assert mockPlayer.rate == 0.5 assert uut.rate == 0.5 assert mockPlayer.position == 0.0 uut.position = 0.5 assert mockPlayer.position == 0.5 assert uut.position == 0.5 assert mockPlayer.volume == 1.0 uut.volume = 0.5 assert mockPlayer.volume == 0.5 assert uut.volume == 0.5 def test_sanitizePlaybackRate(uut): # Initial value assert uut.rate == 1.0 # Valid rates are >= 0.0, invalid is ignored uut.rate = -0.1 assert uut.rate == 1.0 uut.rate = 0.0 assert uut.rate == 0.0 uut.rate = 0.0001 assert uut.rate == 0.0001 uut.rate = 150.0 assert uut.rate == 150.0 def test_sanitizePlaybackPosition(uut): # Initial value assert uut.position == 0.0 # Valid positions are in [0, 1], invalid is limited uut.position = 0.2 assert uut.position == 0.2 uut.position = -0.1 assert uut.position == 0.0 uut.position = 1.0 assert uut.position == 1.0 uut.position = 0.4 assert uut.position == 0.4 uut.position = 1.5 assert uut.position == 1.0 def test_sanitizePlaybackVolume(uut): # Initial value assert uut.volume == 1.0 # Valid volumes are >= 0.0, invalid is ignored uut.volume = -0.1 assert uut.volume == 1.0 uut.volume = 0.0 assert uut.volume == 0.0 uut.volume = 1.0 assert uut.volume == 1.0 uut.volume = 150.0 assert uut.volume == 150.0 def test_addAndSelectSongs(uut, mockPlayer): songs = [ "test.mp3", "test.flac" ] # Songs are added one by one for song in songs: uut.addSong(song) # Songs are not selected automatically assert mockPlayer.currentSong == None assert uut.song == None # Song order is preserved assert uut.songs == songs # Modifying the song list directly has no effect uut.songs.append("something") assert uut.songs == songs # Songs are selected by index for i, s in enumerate(uut.songs): uut.song = i assert mockPlayer.currentSong == uut.songs[i] assert uut.song == i # The current song cannot be de-selected uut.song = None assert uut.song == len(uut.songs) - 1 # Non-existent songs cannot be selected uut.song = -1 assert uut.song == len(uut.songs) - 1 uut.song = 2 assert uut.song == len(uut.songs) - 1 def test_addAndJumpToKeyPoints(uut, mockPlayer): uut.addSong("test.flac") uut.addSong("test.mp3") def checkJump(before, expectedAfter): mockPlayer.position = before uut.jump() assert mockPlayer.position == expectedAfter # Key points are None as long as no song is selected uut.keyPoints = [0.1, 0.2] uut.keyPoint = 0.5 assert uut.keyPoints is None assert uut.keyPoint is None uut.song = 0 # Once a song is selected, jump to start by default assert uut.keyPoint == 0.0 checkJump(0.5, 0.0) # By default songs have an empty list of key points assert uut.keyPoints == [] uut.keyPoints = [0.2, 0.4, 0.1, 0.2] # Added key points are not automatically selected assert uut.keyPoint == 0.0 checkJump(0.1, 0.0) # Any key point can be selected uut.keyPoint = uut.keyPoints[0] checkJump(0.0, uut.keyPoints[0]) uut.keyPoint = 0.5 checkJump(0.0, 0.5) def test_sanitizeKeyPoint(uut): song = "test.flac" uut.addSong(song) uut.song = 0 uut.keyPoints = [0.2, 0.4, 0.1, 0.2, None, -0.5, 1.0, 1.5] # Added key points are automatically de-duplicated, sanitized and sorted to ascending order assert uut.keyPoints == [0.1, 0.2, 0.4] # Key point and key point list cannot be none uut.keyPoint = 0.5 uut.keyPoint = None assert uut.keyPoint == 0.5 uut.keyPoints = None assert uut.keyPoints == [0.1, 0.2, 0.4] # Valid key points are in [0, 1) uut.keyPoint = -0.1 assert uut.keyPoint == 0.5 uut.keyPoint = 1.0 assert uut.keyPoint == 0.5 uut.keyPoint = 0.999 assert uut.keyPoint == 0.999 def test_keyPointsPerSong(uut, mockPlayer): songs = [ ("test.flac", [0.0, 0.5]), ("test.mp3", [0.1]) ] # Key points list is set for the selected song for i, (song, keyPoints) in enumerate(songs): uut.addSong(song) uut.song = i uut.keyPoints = keyPoints # Key points list is automatically loaded when the song selection changes # Active key point is always reset to 0 when song selection changes for i, (song, keyPoints) in enumerate(songs): uut.keyPoint = 0.5 uut.song = i assert uut.keyPoints == keyPoints assert uut.keyPoint == 0.0 # Key points are copied, not stored by reference for i, (song, keyPoints) in enumerate(songs): uut.song = i keyPoints.append(1.0) assert 1.0 not in uut.keyPoints def test_addInexistentSong(uut, mockPlayer): song = "not/a/real/file" with pytest.raises(FileNotFoundError): uut.addSong(song) def test_playingStateNotification(uut, mockPlayer): song = "test.flac" uut.addSong(song) uut.song = 0 called = False receivedValue = None def callback(value): nonlocal called, receivedValue called = True receivedValue = value uut.registerPlayingStateCallback(callback) assert mockPlayer.state == MockPlayer.STOPPED assert not called uut.play() assert called assert receivedValue == True called = False uut.play() assert not called uut.pause() assert called assert receivedValue == False called = False uut.pause() assert not called uut.play() assert called assert receivedValue == True called = False uut.stop() assert called assert receivedValue == False called = False uut.stop() assert not called def test_playbackVolumeNotification(uut, mockPlayer): song = "test.flac" uut.addSong(song) uut.song = 0 called = False receivedValue = None def callback(value): nonlocal called, receivedValue called = True receivedValue = value uut.registerVolumeCallback(callback) assert not called uut.volume = 0.3 assert called assert receivedValue == 0.3 called = False uut.volume = 0.3 assert not called def test_playbackRateNotification(uut, mockPlayer): song = "test.flac" uut.addSong(song) uut.song = 0 called = False receivedValue = None def callback(value): nonlocal called, receivedValue called = True receivedValue = value uut.registerRateCallback(callback) assert not called uut.rate = 0.5 assert called assert receivedValue == 0.5 called = False uut.rate = 0.5 assert not called def test_currentSongNotification(uut): called = False receivedValue = None def callback(value): nonlocal called, receivedValue called = True receivedValue = value uut.registerCurrentSongCallback(callback) assert not called songs = [ "test.flac", "test.mp3" ] # Adding a song does not trigger a notification uut.addSong(songs[0]) assert not called # Selecting a song for the first time triggers uut.song = 0 assert called assert receivedValue == 0 called = False uut.addSong(songs[1]) assert not called # Selecting the same song does not trigger uut.song = 0 assert not called uut.song = 1 assert called assert receivedValue == 1 called = False def test_currentKeyPointNotification(uut): called = False receivedValue = None def callback(value): nonlocal called, receivedValue called = True receivedValue = value uut.registerCurrentKeyPointCallback(callback) assert not called song = "test.flac" uut.addSong(song) uut.song = 0 # Selecting a song for the first time sets the key point to 0.0 assert called assert receivedValue == 0.0 called = False # Changing the key point triggers a notification uut.keyPoint = 0.5 assert called assert receivedValue == 0.5 called = False # Adding list of key points does not trigger a notification uut.keyPoints = [0.2, 0.4] assert not called # Assigning the same key point again does not trigger a notification uut.keyPoint = 0.5 assert not called