emmental/tests/tracklist/test_selection.py

484 lines
20 KiB
Python

# Copyright 2023 (c) Anna Schumaker.
"""Tests our Tracklist Selection OSD."""
import pathlib
import unittest
import emmental.buttons
import emmental.tracklist.selection
import tests.util
import unittest.mock
from gi.repository import Gtk
from gi.repository import Adw
class TestPlaylistRow(unittest.TestCase):
"""Test the PlaylistRow Widget."""
def setUp(self):
"""Set up common variables."""
cover = tests.util.COVER_JPG
self.row = emmental.tracklist.selection.PlaylistRow("name", cover)
def test_init(self):
"""Test that the PlaylistRow Widget is set up properly."""
self.assertIsInstance(self.row, Gtk.ListBoxRow)
self.assertIsInstance(self.row.props.child, Gtk.Box)
self.assertEqual(self.row.name, "name")
self.assertEqual(self.row.image, tests.util.COVER_JPG)
self.assertEqual(self.row.props.child.props.margin_start, 6)
self.assertEqual(self.row.props.child.props.margin_end, 6)
self.assertEqual(self.row.props.child.props.margin_top, 6)
self.assertEqual(self.row.props.child.props.margin_bottom, 6)
self.assertEqual(self.row.props.child.props.spacing, 6)
def test_label(self):
"""Test the PlaylistRow Widget label."""
self.assertIsInstance(self.row._label, Gtk.Label)
self.assertEqual(self.row._label.props.label, "name")
self.assertEqual(self.row._icon.get_next_sibling(), self.row._label)
def test_icon(self):
"""Test the PlaylistRow Widget icon."""
self.assertIsInstance(self.row._icon, Adw.Avatar)
self.assertEqual(self.row.props.child.get_first_child(),
self.row._icon)
self.assertEqual(self.row._icon.get_size(), 32)
self.assertEqual(self.row._icon.get_text(), "name")
self.assertEqual(self.row._icon.get_icon_name(),
"playlist2-symbolic")
fav = emmental.tracklist.selection.PlaylistRow("Favorite Tracks", None)
self.assertEqual(fav._icon.props.icon_name, "heart-filled-symbolic")
queue = emmental.tracklist.selection.PlaylistRow("Queued Tracks", None)
self.assertEqual(queue._icon.props.icon_name, "music-queue-symbolic")
def test_image(self):
"""Test the PlaylistRow Widget image."""
self.assertIsNotNone(self.row._icon.props.custom_image)
none = emmental.tracklist.selection.PlaylistRow("none", None)
self.assertIsNone(none.image)
self.assertIsNone(none._icon.props.custom_image)
later = emmental.tracklist.selection.PlaylistRow("later", None)
later.image = tests.util.COVER_JPG
self.assertIsNotNone(later._icon.props.custom_image)
path = pathlib.Path("/a/b/c.jpg")
inval = emmental.tracklist.selection.PlaylistRow("inval", path)
self.assertIsNone(inval._icon.props.custom_image)
class TestUserTracksFilter(tests.util.TestCase):
"""Test the UserTracksFilter."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.sql.playlists.load(now=True)
self.filter = emmental.tracklist.selection.UserTracksFilter()
self.db_plist = self.sql.playlists.create("Test Playlist")
def test_init(self):
"""Test that the filter is initialized properly."""
self.assertIsInstance(self.filter, Gtk.Filter)
self.assertIsNone(self.filter.playlist)
def test_changed(self):
"""Test the filter changed property."""
changed = unittest.mock.Mock()
self.filter.connect("changed", changed)
self.filter.playlist = self.db_plist
changed.assert_called_with(self.filter, Gtk.FilterChange.DIFFERENT)
def test_match(self):
"""Test the filter match function."""
self.assertTrue(self.filter.match(self.db_plist))
self.db_plist.user_tracks = False
self.assertFalse(self.filter.match(self.db_plist))
self.filter.playlist = self.db_plist
self.db_plist.user_tracks = True
self.assertFalse(self.filter.match(self.db_plist))
class TestPlaylistView(tests.util.TestCase):
"""Test the PlaylistView widget."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.sql.playlists.load(now=True)
self.view = emmental.tracklist.selection.PlaylistView(self.sql)
def test_init(self):
"""Test that the Playlist View is set up properly."""
self.assertIsInstance(self.view, Gtk.ListBox)
self.assertEqual(self.view.props.selection_mode,
Gtk.SelectionMode.NONE)
self.assertTrue(self.view.has_css_class("boxed-list"))
def test_filter_model(self):
"""Test that the filter model has been connected correctly."""
self.assertIsInstance(self.view._filtered, Gtk.FilterListModel)
self.assertIsInstance(self.view._filtered.get_filter(),
emmental.tracklist.selection.UserTracksFilter)
self.assertEqual(self.view._filtered.get_model(), self.sql.playlists)
self.view.playlist = self.sql.playlists.collection
self.assertEqual(self.view._filtered.get_filter().playlist,
self.sql.playlists.collection)
def test_create_func(self):
"""Test that the PlaylistView creates PlaylistRows correctly."""
row = self.view.get_row_at_index(0)
self.assertIsInstance(row, emmental.tracklist.selection.PlaylistRow)
self.assertEqual(row.name, "Favorite Tracks")
self.assertEqual(row.image, None)
self.sql.playlists.favorites.image = tests.util.COVER_JPG
self.assertEqual(row.image, tests.util.COVER_JPG)
def test_activate(self):
"""Test activating a Playlist Row for adding tracks."""
selected = unittest.mock.Mock()
self.view.connect("playlist-selected", selected)
self.view.emit("row-activated", self.view.get_row_at_index(0))
selected.assert_called_with(self.view, self.sql.playlists.favorites)
class TestMoveButtons(unittest.TestCase):
"""Test the Tracklist move up & down buttons."""
def setUp(self):
"""Set up common variables."""
self.move = emmental.tracklist.selection.MoveButtons()
def test_init(self):
"""Test that the move buttons were set up properly."""
self.assertIsInstance(self.move, Gtk.Box)
self.assertTrue(self.move.has_css_class("emmental-move-buttons"))
self.assertTrue(self.move.has_css_class("large-icons"))
self.assertTrue(self.move.has_css_class("linked"))
def test_move_down(self):
"""Test the move down button."""
self.assertIsInstance(self.move._down, Gtk.Button)
self.assertEqual(self.move._down.get_icon_name(), "go-down-symbolic")
self.assertEqual(self.move._down.get_tooltip_text(),
"move selected track down")
self.assertTrue(self.move._down.has_css_class("opaque"))
self.assertTrue(self.move._down.has_css_class("pill"))
self.assertTrue(self.move._down.get_hexpand())
move_down = unittest.mock.Mock()
self.move.connect("move-down", move_down)
self.move._down.emit("clicked")
move_down.assert_called()
self.assertEqual(self.move._up.get_next_sibling(), self.move._down)
def test_can_move_down(self):
"""Test the can-move-down property."""
self.assertFalse(self.move.can_move_down)
self.assertFalse(self.move._down.get_sensitive())
self.move.can_move_down = True
self.assertTrue(self.move._down.get_sensitive())
self.move.can_move_down = False
self.assertFalse(self.move._down.get_sensitive())
def test_move_up(self):
"""Test the move up button."""
self.assertIsInstance(self.move._up, Gtk.Button)
self.assertEqual(self.move._up.get_icon_name(), "go-up-symbolic")
self.assertEqual(self.move._up.get_tooltip_text(),
"move selected track up")
self.assertTrue(self.move._up.has_css_class("opaque"))
self.assertTrue(self.move._up.has_css_class("pill"))
self.assertTrue(self.move._up.get_hexpand())
move_up = unittest.mock.Mock()
self.move.connect("move-up", move_up)
self.move._up.emit("clicked")
move_up.assert_called()
self.assertEqual(self.move.get_first_child(), self.move._up)
def test_can_move_up(self):
"""Test the can-move-up property."""
self.assertFalse(self.move.can_move_up)
self.assertFalse(self.move._up.get_sensitive())
self.move.can_move_up = True
self.assertTrue(self.move._up.get_sensitive())
self.move.can_move_up = False
self.assertFalse(self.move._up.get_sensitive())
class TestOsd(tests.util.TestCase):
"""Test the Tracklist OSD."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.db_plist = self.sql.playlists.create("Test Playlist")
self.playlist = emmental.playlist.playlist.Playlist(self.sql,
self.db_plist)
self.model = Gtk.StringList.new(["Test", "OSD", "Strings"])
self.selection = Gtk.MultiSelection(model=self.model)
self.osd = emmental.tracklist.selection.OSD(self.sql, self.selection)
def test_init(self):
"""Test that the OSD is set up properly."""
self.assertIsInstance(self.osd, Gtk.Overlay)
self.assertIsInstance(self.osd._sizegroup, Gtk.SizeGroup)
self.assertEqual(self.osd.selection, self.selection)
self.assertEqual(self.osd.sql, self.sql)
self.assertIsNone(self.osd.playlist)
self.assertEqual(self.osd._sizegroup.get_mode(),
Gtk.SizeGroupMode.HORIZONTAL)
def test_add_button(self):
"""Test the add tracks button."""
self.assertIsInstance(self.osd._add, emmental.buttons.PopoverButton)
self.assertIsInstance(self.osd._add.get_child(), Adw.ButtonContent)
self.assertIsInstance(self.osd._add.popover_child,
emmental.tracklist.selection.PlaylistView)
self.assertEqual(self.osd._add.get_child().get_icon_name(),
"list-add-symbolic")
self.assertEqual(self.osd._add.get_child().get_label(), "Add")
self.assertEqual(self.osd._add.get_tooltip_text(),
"add selected tracks to a playlist")
self.assertEqual(self.osd._add.get_halign(), Gtk.Align.START)
self.assertEqual(self.osd._add.get_valign(), Gtk.Align.END)
self.assertEqual(self.osd._add.get_margin_start(), 16)
self.assertEqual(self.osd._add.get_margin_bottom(), 16)
self.assertEqual(self.osd._add.get_direction(), Gtk.ArrowType.UP)
self.assertFalse(self.osd._add.get_visible())
self.assertTrue(self.osd._add.has_css_class("suggested-action"))
self.assertTrue(self.osd._add.has_css_class("pill"))
self.assertIn(self.osd._add, self.osd)
self.assertIn(self.osd._add, self.osd._sizegroup.get_widgets())
self.osd.playlist = self.playlist
self.osd.reset()
self.assertEqual(self.osd._add.popover_child.playlist, self.db_plist)
def test_add_button_visible(self):
"""Test the add button visiblity."""
self.assertFalse(self.osd._add.get_visible())
self.selection.select_item(0, True)
self.assertFalse(self.osd._add.get_visible())
self.osd.playlist = self.playlist
self.selection.select_item(1, True)
self.assertTrue(self.osd._remove.get_visible())
def test_add_button_activate(self):
"""Test activating a playlist in the add button."""
with unittest.mock.patch.object(self.db_plist,
"add_track") as mock_add:
self.selection.select_all()
with unittest.mock.patch.object(self.sql, "commit") as mock_commit:
self.osd._add.popover_child.emit("playlist-selected",
self.db_plist)
mock_add.assert_has_calls([unittest.mock.call(self.model[0]),
unittest.mock.call(self.model[1]),
unittest.mock.call(self.model[2])])
mock_commit.assert_called_once()
self.assertEqual(self.osd.n_selected, 0)
def test_remove_button(self):
"""Test the remove tracks button."""
self.assertIsInstance(self.osd._remove, Gtk.Button)
self.assertIsInstance(self.osd._remove.get_child(), Adw.ButtonContent)
self.assertEqual(self.osd._remove.get_child().get_icon_name(),
"list-remove-symbolic")
self.assertEqual(self.osd._remove.get_child().get_label(), "Remove")
self.assertEqual(self.osd._remove.get_tooltip_text(),
"remove selected tracks")
self.assertEqual(self.osd._remove.get_halign(), Gtk.Align.END)
self.assertEqual(self.osd._remove.get_valign(), Gtk.Align.END)
self.assertEqual(self.osd._remove.get_margin_end(), 16)
self.assertEqual(self.osd._remove.get_margin_bottom(), 16)
self.assertTrue(self.osd._remove.has_css_class("destructive-action"))
self.assertTrue(self.osd._remove.has_css_class("pill"))
self.assertIn(self.osd._remove, self.osd)
self.assertIn(self.osd._remove, self.osd._sizegroup.get_widgets())
def test_remove_button_visible(self):
"""Test the remove button visiblity."""
self.assertFalse(self.osd._remove.get_visible())
self.selection.select_item(0, True)
self.assertFalse(self.osd._remove.get_visible())
self.db_plist.user_tracks = False
self.osd.playlist = self.playlist
self.selection.select_item(1, True)
self.assertFalse(self.osd._remove.get_visible())
self.db_plist.user_tracks = True
self.selection.select_item(2, True)
self.assertTrue(self.osd._remove.get_visible())
def test_remove_button_click(self):
"""Test clicking the remove button."""
with unittest.mock.patch.object(self.db_plist,
"remove_track") as mock_remove:
with unittest.mock.patch.object(self.sql, "commit") as mock_commit:
self.selection.select_all()
self.osd._remove.emit("clicked")
mock_remove.assert_not_called()
mock_commit.assert_not_called()
self.osd.playlist = self.playlist
self.selection.select_all()
self.osd._remove.emit("clicked")
mock_remove.assert_has_calls([unittest.mock.call(self.model[i])
for i in range(3)])
mock_commit.assert_called_once()
def test_move_buttons(self):
"""Test the move buttons."""
self.assertIsInstance(self.osd._move,
emmental.tracklist.selection.MoveButtons)
self.assertEqual(self.osd._move.get_halign(), Gtk.Align.CENTER)
self.assertEqual(self.osd._move.get_valign(), Gtk.Align.END)
self.assertEqual(self.osd._move.get_margin_bottom(), 16)
self.assertIn(self.osd._move, self.osd._sizegroup.get_widgets())
self.assertIn(self.osd._move, self.osd)
def test_move_button_clicks(self):
"""Test clicking the move buttons."""
set_incremental = unittest.mock.Mock()
self.model.set_incremental = set_incremental
self.osd.playlist = self.playlist
self.selection.select_item(1, True)
with unittest.mock.patch.object(self.playlist,
"move_track_down") as mock_move_down:
with unittest.mock.patch.object(self.sql, "commit") as mock_commit:
self.osd._move.emit("move-down")
mock_move_down.assert_called_with(self.model[1])
set_incremental.assert_has_calls([unittest.mock.call(False),
unittest.mock.call(True)])
mock_commit.assert_called_once()
set_incremental.reset_mock()
with unittest.mock.patch.object(self.playlist,
"move_track_up") as mock_move_up:
with unittest.mock.patch.object(self.sql, "commit") as mock_commit:
self.osd._move.emit("move-up")
mock_move_up.assert_called_with(self.model[1])
set_incremental.assert_has_calls([unittest.mock.call(False),
unittest.mock.call(True)])
mock_commit.assert_called_once()
def test_move_buttons_sensitive(self):
"""Test the move button sensitivity."""
self.assertFalse(self.osd._move.get_sensitive())
self.osd.playlist = self.playlist
self.selection.select_item(0, True)
self.assertTrue(self.osd._move.get_sensitive())
self.assertTrue(self.osd._move.can_move_down)
self.assertFalse(self.osd._move.can_move_up)
self.selection.select_item(1, True)
self.assertTrue(self.osd._move.get_sensitive())
self.assertTrue(self.osd._move.can_move_down)
self.assertTrue(self.osd._move.can_move_up)
self.selection.select_item(2, True)
self.assertTrue(self.osd._move.get_sensitive())
self.assertFalse(self.osd._move.can_move_down)
self.assertTrue(self.osd._move.can_move_up)
self.selection.select_item(1, False)
self.assertFalse(self.osd._move.get_sensitive())
def test_move_buttons_visible(self):
"""Test the move button visibility."""
self.assertFalse(self.osd._move.get_visible())
self.selection.select_item(0, True)
self.assertFalse(self.osd._move.get_visible())
self.osd.playlist = self.playlist
self.selection.select_item(1, True)
self.assertTrue(self.osd._move.get_visible())
self.db_plist.tracks_movable = False
self.selection.select_item(2, True)
self.assertFalse(self.osd._move.get_visible())
def test_selection_properties(self):
"""Test updating properties when the selection changes."""
self.assertFalse(self.osd.have_selected)
self.assertEqual(self.osd.n_selected, 0)
self.selection.select_item(1, True)
self.assertTrue(self.osd.have_selected)
self.assertEqual(self.osd.n_selected, 1)
def test_clear_selection(self):
"""Test the clear_selection() function."""
self.selection.select_item(1, True)
self.osd.clear_selection()
self.assertEqual(self.selection.get_selection().get_size(), 0)
self.assertEqual(self.osd.n_selected, 0)
self.assertFalse(self.osd.have_selected)
self.osd.playlist = self.playlist
self.assertEqual(self.selection.get_selection().get_size(), 0)
self.assertEqual(self.osd.n_selected, 0)
self.assertFalse(self.osd.have_selected)
def test_reset(self):
"""Test the reset() function."""
self.osd.have_selected = True
with unittest.mock.patch.object(self.selection,
"unselect_all") as mock_unselect:
self.osd.reset()
mock_unselect.assert_called()
self.assertFalse(self.osd.have_selected)
def test_accelerators(self):
"""Test that the accelerators list is set up properly."""
entries = [("remove-selected-tracks", self.osd._remove.activate,
["Delete"], self.osd._remove, "visible"),
("move-track-up", self.osd._move._up.activate,
["<Control>Up"], self.osd._move, "can-move-up"),
("move-track-down", self.osd._move._down.activate,
["<Control>Down"], self.osd._move, "can-move-down")]
accels = self.osd.accelerators
self.assertIsInstance(accels, list)
for i, (name, func, accel, gobject, prop) in enumerate(entries):
with self.subTest(action=name):
self.assertIsInstance(accels[i], emmental.action.ActionEntry)
self.assertEqual(accels[i].name, name)
self.assertEqual(accels[i].func, func)
self.assertEqual(accels[i].accels, accel)
if gobject and prop:
enabled = gobject.get_property(prop)
self.assertEqual(accels[i].enabled, enabled)
gobject.set_property(prop, not enabled)
self.assertEqual(accels[i].enabled, not enabled)