509 lines
22 KiB
Python
509 lines
22 KiB
Python
# Copyright 2022 (c) Anna Schumaker
|
|
"""Tests our ListStore and Playlist objects."""
|
|
import pathlib
|
|
import unittest
|
|
import unittest.mock
|
|
import emmental.db.playlist
|
|
import tests.util.playlist
|
|
from gi.repository import Gio
|
|
from gi.repository import Gtk
|
|
|
|
|
|
class TestPlaylistRow(unittest.TestCase):
|
|
"""Tests our shared Playlist Row."""
|
|
|
|
def setUp(self):
|
|
"""Set up common variables."""
|
|
self.table = Gio.ListStore()
|
|
self.table.add_track = unittest.mock.Mock(return_value=True)
|
|
self.table.remove_track = unittest.mock.Mock(return_value=True)
|
|
self.table.move_track_down = unittest.mock.Mock(return_value=True)
|
|
self.table.move_track_up = unittest.mock.Mock(return_value=True)
|
|
self.table.get_trackids = unittest.mock.Mock(return_value={1, 2, 3})
|
|
self.table.get_track_order = unittest.mock.Mock()
|
|
self.table.refilter = unittest.mock.Mock()
|
|
self.table.queue = emmental.db.idle.Queue()
|
|
self.table.update = unittest.mock.Mock(return_value=True)
|
|
|
|
self.playlist = emmental.db.playlist.Playlist(table=self.table,
|
|
propertyid=0,
|
|
name="Test Playlist")
|
|
self.track = emmental.db.tracks.Track(table=None, trackid=12345)
|
|
|
|
def test_init(self):
|
|
"""Test that the Playlist object is configured correctly."""
|
|
self.assertIsInstance(self.playlist, emmental.db.table.Row)
|
|
self.assertIsInstance(self.playlist.tracks,
|
|
emmental.db.tracks.TrackidSet)
|
|
self.assertEqual(self.playlist.table, self.table)
|
|
|
|
self.assertEqual(self.playlist.propertyid, 0)
|
|
self.assertEqual(self.playlist.name, "Test Playlist")
|
|
self.assertFalse(self.playlist.active)
|
|
|
|
self.assertEqual(self.playlist.loop, "None")
|
|
self.assertFalse(self.playlist.shuffle)
|
|
self.assertEqual(self.playlist.current_trackid, 0)
|
|
self.assertEqual(self.playlist.sort_order, "")
|
|
|
|
self.assertEqual(self.playlist.n_tracks, 0)
|
|
self.assertFalse(self.playlist.user_tracks)
|
|
self.assertFalse(self.playlist.tracks_loaded)
|
|
|
|
playlist2 = emmental.db.playlist.Playlist(table=self.table,
|
|
propertyid=1,
|
|
name="Test Name", active=1,
|
|
shuffle=True, loop="Track",
|
|
current_trackid=42,
|
|
sort_order="Track Number")
|
|
self.assertEqual(playlist2.propertyid, 1)
|
|
self.assertEqual(playlist2.name, "Test Name")
|
|
self.assertEqual(playlist2.current_trackid, 42)
|
|
self.assertEqual(playlist2.sort_order, "Track Number")
|
|
self.assertEqual(playlist2.loop, "Track")
|
|
self.assertTrue(playlist2.shuffle)
|
|
self.assertTrue(playlist2.active)
|
|
|
|
def test_children(self):
|
|
"""Test the child playlist properties."""
|
|
self.assertIsNone(self.playlist.child_set)
|
|
self.assertIsNone(self.playlist.children)
|
|
|
|
table = emmental.db.table.Table(None)
|
|
self.playlist.add_children(table, set())
|
|
|
|
self.assertIsInstance(self.playlist.child_set,
|
|
emmental.db.table.TableSubset)
|
|
self.assertEqual(self.playlist.child_set.table, table)
|
|
self.assertSetEqual(self.playlist.child_set.keyset.keys, set())
|
|
|
|
self.assertIsInstance(self.playlist.children, Gtk.FilterListModel)
|
|
self.assertEqual(self.playlist.children.get_filter(),
|
|
table.get_filter())
|
|
self.assertEqual(self.playlist.children.get_model(),
|
|
self.playlist.child_set)
|
|
self.assertFalse(self.playlist.children.get_incremental())
|
|
|
|
playlist2 = emmental.db.playlist.Playlist(table=self.table,
|
|
propertyid=2, name="Plist2")
|
|
playlist2.add_children(table, {1, 2, 3})
|
|
self.assertSetEqual(playlist2.child_set.keyset.keys, {1, 2, 3})
|
|
|
|
def test_parent(self):
|
|
"""Test the parent playlist property."""
|
|
self.assertIsNone(self.playlist.parent)
|
|
|
|
def test_do_update(self):
|
|
"""Test the do_update() function."""
|
|
for (prop, value) in [("name", "New Name"), ("propertyid", 12345),
|
|
("children", Gtk.FilterListModel()),
|
|
("n-tracks", 42), ("user-tracks", True),
|
|
("tracks-loaded", True),
|
|
("tracks-movable", True)]:
|
|
with self.subTest(property=prop):
|
|
self.playlist.set_property(prop, value)
|
|
self.table.update.assert_not_called()
|
|
|
|
for (prop, value) in [("active", True), ("loop", "Track"),
|
|
("shuffle", True), ("sort-order", "my order"),
|
|
("current-trackid", 42)]:
|
|
with self.subTest(property=prop):
|
|
self.playlist.set_property(prop, value)
|
|
self.table.update.assert_called_with(self.playlist,
|
|
prop, value)
|
|
|
|
def test_add_child(self):
|
|
"""Test adding a child playlist to the playlist."""
|
|
table = emmental.db.table.Table(None)
|
|
child1 = tests.util.table.MockRow(table=table, number=1)
|
|
child2 = tests.util.table.MockRow(table=table, number=2)
|
|
self.playlist.add_children(table, set())
|
|
|
|
self.playlist.add_child(child1)
|
|
self.assertIn(child1, self.playlist.child_set)
|
|
self.table.refilter.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
|
|
|
self.playlist.add_child(child2)
|
|
self.table.refilter.assert_called_once()
|
|
|
|
def test_add_track(self):
|
|
"""Test adding a track to the playlist."""
|
|
self.playlist.add_track(self.track, idle=True)
|
|
self.table.add_track.assert_called_with(self.playlist, self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.playlist._Playlist__add_track, self.track))
|
|
|
|
self.playlist.add_track(self.track)
|
|
self.table.add_track.assert_called_with(self.playlist, self.track)
|
|
self.assertIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.playlist.n_tracks, 1)
|
|
|
|
self.playlist.tracks.trackids.clear()
|
|
self.table.add_track.return_value = False
|
|
self.playlist.add_track(self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
|
|
def test_get_track_order(self):
|
|
"""Test the get_track_order() function."""
|
|
self.table.get_track_order.return_value = {1: 3, 2: 2, 3: 1}
|
|
self.assertDictEqual(self.playlist.get_track_order(),
|
|
{1: 3, 2: 2, 3: 1})
|
|
self.table.get_track_order.assert_called_with(self.playlist)
|
|
|
|
def test_has_child(self):
|
|
"""Test the playlist has_child() function."""
|
|
table = emmental.db.table.Table(None)
|
|
child = tests.util.table.MockRow(table=table, number=1)
|
|
self.playlist.add_children(table, set())
|
|
|
|
self.assertFalse(self.playlist.has_child(child))
|
|
self.playlist.add_child(child)
|
|
self.assertTrue(self.playlist.has_child(child))
|
|
self.playlist.remove_child(child)
|
|
self.assertFalse(self.playlist.has_child(child))
|
|
|
|
def test_has_track(self):
|
|
"""Test the playlist has_track() function."""
|
|
self.assertFalse(self.playlist.has_track(self.track))
|
|
self.playlist.add_track(self.track)
|
|
self.assertTrue(self.playlist.has_track(self.track))
|
|
self.playlist.remove_track(self.track)
|
|
self.assertFalse(self.playlist.has_track(self.track))
|
|
|
|
def test_move_tracks(self):
|
|
"""Test the move_track_up() and move_track_down() functions."""
|
|
self.assertFalse(self.playlist.tracks_movable)
|
|
|
|
self.assertTrue(self.playlist.move_track_down(self.track))
|
|
self.table.move_track_down.assert_called_with(self.playlist,
|
|
self.track)
|
|
|
|
self.assertTrue(self.playlist.move_track_up(self.track))
|
|
self.table.move_track_up.assert_called_with(self.playlist, self.track)
|
|
|
|
def test_remove_child(self):
|
|
"""Test removing a child playlist from the playlist."""
|
|
table = emmental.db.table.Table(None)
|
|
child1 = tests.util.table.MockRow(table=table, number=1)
|
|
child2 = tests.util.table.MockRow(table=table, number=2)
|
|
self.playlist.add_children(table, set())
|
|
self.playlist.add_child(child1)
|
|
self.playlist.add_child(child2)
|
|
self.table.refilter.reset_mock()
|
|
|
|
self.playlist.remove_child(child1)
|
|
self.assertFalse(child1 in self.playlist.child_set)
|
|
self.table.refilter.assert_not_called()
|
|
|
|
self.playlist.remove_child(child2)
|
|
self.table.refilter.assert_called_with(Gtk.FilterChange.MORE_STRICT)
|
|
|
|
def test_remove_track(self):
|
|
"""Test removing a track from the playlist."""
|
|
self.playlist.tracks.trackids.add(self.track.trackid)
|
|
self.playlist.remove_track(self.track, idle=True)
|
|
self.table.remove_track.assert_not_called()
|
|
self.assertIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.playlist._Playlist__remove_track, self.track))
|
|
|
|
self.playlist.remove_track(self.track)
|
|
self.table.remove_track.assert_called_with(self.playlist, self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.playlist.n_tracks, 0)
|
|
|
|
self.playlist.tracks.trackids.add(self.track.trackid)
|
|
self.table.remove_track.return_value = False
|
|
self.playlist.remove_track(self.track)
|
|
self.table.remove_track.assert_called_with(self.playlist, self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
|
|
def test_load_tracks(self):
|
|
"""Test loading a Playlist's Tracks."""
|
|
self.assertEqual(self.playlist.n_tracks, 0)
|
|
self.assertFalse(self.playlist.tracks_loaded)
|
|
|
|
self.assertTrue(self.playlist.load_tracks())
|
|
self.table.get_trackids.assert_called_with(self.playlist)
|
|
self.assertSetEqual(self.playlist.tracks.trackids, {1, 2, 3})
|
|
self.assertTrue(self.playlist.tracks_loaded)
|
|
|
|
self.table.get_trackids.reset_mock()
|
|
self.assertTrue(self.playlist.load_tracks())
|
|
self.table.get_trackids.assert_not_called()
|
|
|
|
def test_reload_tracks(self):
|
|
"""Test reloading a Playlist's Tracks."""
|
|
self.playlist.tracks_loaded = True
|
|
self.playlist.reload_tracks(idle=True)
|
|
self.table.get_trackids.assert_not_called()
|
|
self.assertEqual(self.table.queue[0], (self.playlist.load_tracks,))
|
|
|
|
self.playlist.tracks_loaded = True
|
|
self.playlist.reload_tracks()
|
|
self.table.get_trackids.assert_called_with(self.playlist)
|
|
self.assertSetEqual(self.playlist.tracks.trackids, {1, 2, 3})
|
|
|
|
|
|
class TestPlaylistTable(tests.util.TestCase):
|
|
"""Tests our Playlist Table."""
|
|
|
|
def setUp(self):
|
|
"""Set up common variables."""
|
|
super().setUp()
|
|
self.table = tests.util.playlist.MockTable(self.sql)
|
|
self.sql("DELETE FROM playlist_properties")
|
|
|
|
self.library = self.sql.libraries.create(pathlib.Path("/a/b"))
|
|
self.album = self.sql.albums.create("Test Album", "Artist", "2023-03")
|
|
self.medium = self.sql.media.create(self.album, "", number=1)
|
|
self.year = self.sql.years.create(2023)
|
|
self.track = self.sql.tracks.create(self.library,
|
|
pathlib.Path("/a/b/c.ogg"),
|
|
self.medium, self.year)
|
|
|
|
def test_treemodel(self):
|
|
"""Check that the table's treemodel was set up properly."""
|
|
self.assertIsInstance(self.table.treemodel, Gtk.TreeListModel)
|
|
self.assertEqual(self.table.treemodel.get_model(), self.table)
|
|
self.assertFalse(self.table.treemodel.get_passthrough())
|
|
self.assertFalse(self.table.treemodel.get_autoexpand())
|
|
|
|
root = self.table.create("Root")
|
|
self.assertIsNone(self.table._Table__create_tree(root))
|
|
root.children = Gtk.FilterListModel()
|
|
self.assertEqual(self.table._Table__create_tree(root), root.children)
|
|
|
|
def test_add_track(self):
|
|
"""Test adding a track to a playlist."""
|
|
self.assertTrue(self.table.system_tracks)
|
|
|
|
plist = self.table.create("Test Playlist")
|
|
self.library.deleting = True
|
|
self.assertFalse(self.table.add_track(plist, None))
|
|
self.assertFalse(self.table.add_track(plist, self.track))
|
|
|
|
self.library.deleting = False
|
|
self.assertTrue(self.table.add_track(plist, self.track))
|
|
cur = self.sql("SELECT COUNT(*) FROM system_tracks")
|
|
self.assertEqual(cur.fetchone()["COUNT(*)"], 1)
|
|
self.assertFalse(self.table.add_track(plist, self.track))
|
|
|
|
self.table.system_tracks = False
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.add_track(plist, self.track)
|
|
|
|
def test_construct(self):
|
|
"""Test constructing a new playlist."""
|
|
self.assertIsNone(self.table.active_playlist)
|
|
|
|
plist1 = self.table.construct(propertyid=1, name="Test")
|
|
self.assertIsInstance(plist1, emmental.db.playlist.Playlist)
|
|
self.assertEqual(plist1.table, self.table)
|
|
self.assertEqual(plist1.propertyid, 1)
|
|
self.assertEqual(plist1.name, "Test")
|
|
self.assertFalse(plist1.active)
|
|
|
|
plist2 = self.table.construct(propertyid=2, name="Test 2", active=True)
|
|
self.assertEqual(self.table.active_playlist, plist2)
|
|
self.assertEqual(self.sql.active_playlist, plist2)
|
|
self.assertTrue(plist2.active)
|
|
|
|
self.assertEqual(self.table.queue[0], (plist1.load_tracks,))
|
|
self.assertEqual(self.table.queue[1], (plist2.load_tracks,))
|
|
|
|
def test_get_sort_key(self):
|
|
"""Test getting a sort key for a playlist."""
|
|
plist = self.table.create("Playlist 1")
|
|
self.assertTupleEqual(self.table.get_sort_key(plist),
|
|
("playlist", "1"))
|
|
|
|
def test_clear(self):
|
|
"""Test clearing the active_playlist property."""
|
|
plist = self.table.create("Playlist 1")
|
|
self.table.active_playlist = plist
|
|
self.table.clear()
|
|
self.assertIsNone(self.table.active_playlist)
|
|
self.assertEqual(len(self.table), 0)
|
|
|
|
def test_delete(self):
|
|
"""Test deleting the active playlist with system tracks."""
|
|
self.table.system_tracks = True
|
|
plist = self.table.create("Test Playlist")
|
|
plist.add_track(self.track)
|
|
self.sql.set_active_playlist(plist)
|
|
|
|
self.assertTrue(self.table.delete(plist))
|
|
self.assertIsNone(self.table.active_playlist)
|
|
self.assertIsNone(self.sql.active_playlist)
|
|
self.assertNotIn(plist, self.table)
|
|
|
|
cur = self.sql("SELECT COUNT(*) FROM system_tracks")
|
|
self.assertEqual(cur.fetchone()["COUNT(*)"], 0)
|
|
|
|
def test_get_trackids(self):
|
|
"""Test getting the set of trackids for this playlist."""
|
|
self.assertTrue(self.table.system_tracks)
|
|
self.table._Table__autodelete = unittest.mock.Mock()
|
|
|
|
plist = self.table.create("Test Playlist")
|
|
self.assertSetEqual(self.table.get_trackids(plist), set())
|
|
self.table._Table__autodelete.assert_called_with(plist)
|
|
|
|
plist.add_track(self.track)
|
|
self.assertSetEqual(self.table.get_trackids(plist),
|
|
{self.track.trackid})
|
|
|
|
self.library.deleting = True
|
|
self.assertSetEqual(self.table.get_trackids(plist), set())
|
|
|
|
self.table.system_tracks = False
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.get_trackids(plist)
|
|
|
|
def test_get_track_order(self):
|
|
"""Test getting track sort keys for a playlist."""
|
|
plist = self.table.create("Test Playlist")
|
|
plist.sort_order = "my order"
|
|
self.sql.tracks.map_sort_order = unittest.mock.Mock()
|
|
self.sql.tracks.map_sort_order.return_value = {1: 3, 2: 2, 3: 1}
|
|
self.assertDictEqual(self.table.get_track_order(plist),
|
|
{1: 3, 2: 2, 3: 1})
|
|
self.sql.tracks.map_sort_order.assert_called_with("my order")
|
|
|
|
self.sql.tracks.map_sort_order.reset_mock()
|
|
plist.tracks_movable = True
|
|
self.assertDictEqual(self.table.get_track_order(plist),
|
|
{1: 3, 2: 2, 3: 1})
|
|
self.sql.tracks.map_sort_order.assert_called_with("my order")
|
|
|
|
plist.sort_order = "user"
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.get_track_order(plist)
|
|
|
|
def test_move_track_down(self):
|
|
"""Test moving tracks down in the sort order."""
|
|
plist = self.table.create("Test Playlist")
|
|
self.assertFalse(self.table.move_track_down(plist, self.track))
|
|
|
|
plist.tracks_movable = True
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.move_track_down(plist, self.track)
|
|
|
|
self.table.do_move_track_down = unittest.mock.Mock(return_value=False)
|
|
self.table.move_track_down(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "")
|
|
|
|
self.table.do_move_track_down.return_value = True
|
|
self.table.move_track_down(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "user")
|
|
|
|
def test_move_track_up(self):
|
|
"""Test moving tracks up in the sort order."""
|
|
plist = self.table.create("Test Playlist")
|
|
self.assertFalse(self.table.move_track_up(plist, self.track))
|
|
|
|
plist.tracks_movable = True
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.move_track_up(plist, self.track)
|
|
|
|
self.table.do_move_track_up = unittest.mock.Mock(return_value=False)
|
|
self.table.move_track_up(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "")
|
|
|
|
self.table.do_move_track_up.return_value = True
|
|
self.table.move_track_up(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "user")
|
|
|
|
def test_refilter(self):
|
|
"""Test refiltering the playlist table."""
|
|
self.table.queue.push(unittest.mock.Mock())
|
|
|
|
with unittest.mock.patch.object(self.table.get_filter(),
|
|
"changed") as mock_changed:
|
|
self.table.refilter(Gtk.FilterChange.MORE_STRICT)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.table._Table__refilter,
|
|
Gtk.FilterChange.MORE_STRICT))
|
|
mock_changed.assert_not_called()
|
|
|
|
self.table.refilter(Gtk.FilterChange.LESS_STRICT)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.table._Table__refilter,
|
|
Gtk.FilterChange.LESS_STRICT))
|
|
mock_changed.assert_not_called()
|
|
|
|
self.table.queue.complete()
|
|
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
|
|
|
def test_remove_track(self):
|
|
"""Test adding a track to a playlist."""
|
|
self.assertTrue(self.table.system_tracks)
|
|
self.table._Table__autodelete = unittest.mock.Mock()
|
|
|
|
plist = self.table.create("Test Playlist")
|
|
plist.add_track(self.track)
|
|
self.assertTrue(self.table.remove_track(plist, self.track))
|
|
cur = self.sql("SELECT COUNT(*) FROM system_tracks")
|
|
self.assertEqual(cur.fetchone()["COUNT(*)"], 0)
|
|
self.assertFalse(self.table.remove_track(plist, self.track))
|
|
self.table._Table__autodelete.assert_called_with(plist)
|
|
|
|
self.table.system_tracks = False
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.remove_track(plist, self.track)
|
|
|
|
def test_update(self):
|
|
"""Test updating playlist properties."""
|
|
plist1 = self.table.create("Test Playlist 1")
|
|
plist2 = self.table.create("Test Playlist 2")
|
|
plist1.active = True
|
|
plist1.loop = "Track"
|
|
plist1.shuffle = True
|
|
plist1.sort_order = "Track Number"
|
|
plist1.current_trackid = self.track.trackid
|
|
|
|
self.assertEqual(self.table.active_playlist, plist1)
|
|
row = self.sql("""SELECT active, loop, shuffle,
|
|
current_trackid, sort_order
|
|
FROM playlist_properties
|
|
WHERE propertyid=?""", plist1.propertyid).fetchone()
|
|
self.assertEqual(row["active"], True)
|
|
self.assertEqual(row["current_trackid"], self.track.trackid)
|
|
self.assertEqual(row["sort_order"], "Track Number")
|
|
self.assertEqual(row["loop"], "Track")
|
|
self.assertTrue(row["shuffle"])
|
|
|
|
plist1.current_trackid = 0
|
|
plist2.active = True
|
|
self.assertEqual(self.table.active_playlist, plist2)
|
|
row = self.sql("""SELECT active, current_trackid
|
|
FROM playlist_properties WHERE propertyid=?""",
|
|
plist1.propertyid).fetchone()
|
|
self.assertEqual(row["active"], False)
|
|
self.assertEqual(row["current_trackid"], None)
|
|
|
|
def test_autodelete(self):
|
|
"""Test automatically deleting playlists."""
|
|
plist = self.table.create("Test Playlist")
|
|
self.table.queue.cancel()
|
|
|
|
self.assertFalse(self.table.autodelete)
|
|
self.table._Table__autodelete(plist)
|
|
self.assertFalse(self.table.queue.running)
|
|
self.assertIn(plist, self.table)
|
|
|
|
self.table.autodelete = True
|
|
self.table._Table__autodelete(plist)
|
|
self.assertTupleEqual(self.table.queue[0],
|
|
(self.table._Table__do_autodelete, plist))
|
|
|
|
plist.n_tracks = 1
|
|
self.assertTrue(self.table._Table__do_autodelete(plist))
|
|
self.assertIn(plist, self.table)
|
|
|
|
plist.n_tracks = 0
|
|
self.assertTrue(self.table._Table__do_autodelete(plist))
|
|
self.assertNotIn(plist, self.table)
|