emmental/tests/db/test_playlist.py

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)