# 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, ["Up"], self.osd._move, "can-move-up"), ("move-track-down", self.osd._move._down.activate, ["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)