# 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 TestPlaylistRowWidget(unittest.TestCase): """Test the Playlist Row Widget.""" def setUp(self): """Set up common variables.""" self.widget = emmental.tracklist.selection.PlaylistRowWidget() def test_init(self): """Test that the Playlist Row Widget is set up properly.""" self.assertIsInstance(self.widget, Gtk.Box) def test_label(self): """Test the Playlist Row Widget label.""" self.widget.name = "Test Playlist Name" self.assertIsInstance(self.widget._label, Gtk.Label) self.assertEqual(self.widget._label.get_text(), "Test Playlist Name") self.assertEqual(self.widget._label.get_xalign(), 0.0) self.assertEqual(self.widget._icon.get_next_sibling(), self.widget._label) def test_icon(self): """Test the Playlist Row Widget icon.""" self.assertIsInstance(self.widget._icon, Adw.Avatar) self.assertEqual(self.widget.get_first_child(), self.widget._icon) self.assertEqual(self.widget._icon.get_size(), 32) self.widget.name = "Favorite Tracks" self.assertEqual(self.widget._icon.get_icon_name(), "heart-filled-symbolic") self.assertEqual(self.widget._icon.get_text(), "Favorite Tracks") self.widget.name = "Queued Tracks" self.assertEqual(self.widget._icon.get_icon_name(), "music-queue-symbolic") self.assertEqual(self.widget._icon.get_text(), "Queued Tracks") self.widget.name = "Any Other Name" self.assertEqual(self.widget._icon.get_icon_name(), "playlist2-symbolic") self.assertEqual(self.widget._icon.get_text(), "Any Other Name") def test_image(self): """Test the Playlist Row Widget image.""" self.assertIsNone(self.widget.image) self.widget.image = tests.util.COVER_JPG self.assertIsNotNone(self.widget._icon.get_custom_image()) self.widget.image = None self.assertIsNone(self.widget._icon.get_custom_image()) self.widget.image = pathlib.Path("/a/b/c.jpg") self.assertIsNone(self.widget._icon.get_custom_image()) class TestPlaylistRow(tests.util.TestCase): """Test the PlaylistRow widget.""" def setUp(self): """Set up common variables.""" super().setUp() self.playlist = self.sql.playlists.create("Test Playlist") self.listitem = Gtk.ListItem() self.listitem.get_item = unittest.mock.Mock(return_value=self.playlist) self.row = emmental.tracklist.selection.PlaylistRow(self.listitem) def test_init(self): """Test that the PlaylistRow is initialized properly.""" self.assertIsInstance(self.row, emmental.factory.ListRow) self.assertIsInstance(self.row.child, emmental.tracklist.selection.PlaylistRowWidget) def test_bind(self): """Test binding the PlaylistRow.""" self.row.bind() self.assertEqual(self.row.child.name, "Test Playlist") self.assertIsNone(self.row.child.image) self.playlist.image = pathlib.Path("/a/b/c.jpg") self.assertEqual(self.row.child.image, pathlib.Path("/a/b/c.jpg")) 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.ListView) self.assertTrue(self.view.get_show_separators()) self.assertTrue(self.view.get_single_click_activate()) self.assertTrue(self.view.has_css_class("rich-list")) def test_models(self): """Test that the models have been connected correctly.""" self.assertIsInstance(self.view._selection, Gtk.NoSelection) self.assertIsInstance(self.view._filtered, Gtk.FilterListModel) self.assertIsInstance(self.view._filtered.get_filter(), emmental.tracklist.selection.UserTracksFilter) self.assertEqual(self.view.get_model(), self.view._selection) self.assertEqual(self.view._selection.get_model(), self.view._filtered) self.assertEqual(self.view._filtered.get_model(), self.sql.playlists) def test_factory(self): """Test that the factory has been configured correctly.""" self.assertIsInstance(self.view._factory, emmental.factory.Factory) self.assertEqual(self.view.get_factory(), self.view._factory) self.assertEqual(self.view._factory.row_type, emmental.tracklist.selection.PlaylistRow) 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("activate", 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.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.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_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_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)