Compare commits
12 Commits
ba4907ec34
...
ef99951f74
Author | SHA1 | Date |
---|---|---|
Anna Schumaker | ef99951f74 | |
Anna Schumaker | 0fd391a9fd | |
Anna Schumaker | bc92e72265 | |
Anna Schumaker | 8dae0ed7bd | |
Anna Schumaker | 1707f87e45 | |
Anna Schumaker | 7e99fd1ba0 | |
Anna Schumaker | a8e7078308 | |
Anna Schumaker | 1d0813f217 | |
Anna Schumaker | 725619faf5 | |
Anna Schumaker | 6607e5b0ad | |
Anna Schumaker | 73019d8eb4 | |
Anna Schumaker | 6032e549a5 |
|
@ -21,7 +21,7 @@ from gi.repository import Adw
|
|||
|
||||
MAJOR_VERSION = 3
|
||||
MINOR_VERSION = 0
|
||||
MICRO_VERSION = 4
|
||||
MICRO_VERSION = 5
|
||||
|
||||
VERSION_NUMBER = f"{MAJOR_VERSION}.{MINOR_VERSION}.{MICRO_VERSION}"
|
||||
VERSION_STRING = f"Emmental {VERSION_NUMBER}{gsetup.DEBUG_STR}"
|
||||
|
@ -182,6 +182,7 @@ class Application(Adw.Application):
|
|||
side_bar = sidebar.Card(sql=self.db)
|
||||
self.db.settings.bind_setting("sidebar.artists.show-all", side_bar,
|
||||
"show-all-artists")
|
||||
self.__add_accelerators(side_bar.accelerators)
|
||||
return side_bar
|
||||
|
||||
def build_tracklist(self) -> tracklist.Card:
|
||||
|
|
|
@ -52,6 +52,11 @@ class Queue(GObject.GObject):
|
|||
self.running = False
|
||||
self._idle_id = None
|
||||
|
||||
def cancel_task(self, func: typing.Callable) -> None:
|
||||
"""Remove all instances of a specific task from the Idle Queue."""
|
||||
self._tasks = [t for t in self._tasks if t[0] != func]
|
||||
self.__update_counters()
|
||||
|
||||
def complete(self) -> None:
|
||||
"""Complete all pending tasks."""
|
||||
if self.running:
|
||||
|
@ -60,12 +65,13 @@ class Queue(GObject.GObject):
|
|||
self.cancel()
|
||||
|
||||
def push(self, func: typing.Callable, *args,
|
||||
now: bool = False) -> bool | None:
|
||||
now: bool = False, first: bool = False) -> bool | None:
|
||||
"""Add a task to the Idle Queue."""
|
||||
if not self.enabled or now:
|
||||
return func(*args)
|
||||
|
||||
self._tasks.append((func, *args))
|
||||
pos = 0 if first else len(self._tasks)
|
||||
self._tasks.insert(pos, (func, *args))
|
||||
self.total += 1
|
||||
self.__start()
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ class Playlist(table.Row):
|
|||
self.child_set = table.TableSubset(child_table, keys=child_keys)
|
||||
self.children = Gtk.FilterListModel.new(self.child_set,
|
||||
child_table.get_filter())
|
||||
self.children.set_incremental(True)
|
||||
|
||||
def do_update(self, column: str) -> bool:
|
||||
"""Update a Playlist object."""
|
||||
|
@ -69,6 +68,8 @@ class Playlist(table.Row):
|
|||
def add_child(self, child: typing.Self) -> None:
|
||||
"""Add a child Playlist to this Playlist."""
|
||||
self.child_set.add_row(child)
|
||||
if self.child_set.keyset.n_keys == 1:
|
||||
self.table.refilter(Gtk.FilterChange.LESS_STRICT)
|
||||
|
||||
def add_track(self, track: Track, *, idle: bool = False) -> None:
|
||||
"""Add a Track to this Playlist."""
|
||||
|
@ -110,6 +111,8 @@ class Playlist(table.Row):
|
|||
def remove_child(self, child: typing.Self) -> None:
|
||||
"""Remove a child Playlist from this Playlist."""
|
||||
self.child_set.remove_row(child)
|
||||
if self.child_set.keyset.n_keys == 0:
|
||||
self.table.refilter(Gtk.FilterChange.MORE_STRICT)
|
||||
|
||||
def remove_track(self, track: table.Row, *, idle: bool = False) -> None:
|
||||
"""Remove a Track from this Playlist."""
|
||||
|
@ -154,6 +157,10 @@ class Table(table.Table):
|
|||
def __create_tree(self, plist: Playlist) -> Gtk.FilterListModel | None:
|
||||
return plist.children
|
||||
|
||||
def __refilter(self, change_how: Gtk.FilterChange) -> bool:
|
||||
self.get_filter().changed(change_how)
|
||||
return True
|
||||
|
||||
def do_add_track(self, playlist: Playlist, track: Track) -> bool:
|
||||
"""Add a Track to the Playlist."""
|
||||
raise NotImplementedError
|
||||
|
@ -255,6 +262,11 @@ class Table(table.Table):
|
|||
playlist.sort_order = "user"
|
||||
return res
|
||||
|
||||
def refilter(self, change_how: Gtk.FilterChange) -> None:
|
||||
"""Schedule refiltering the Table."""
|
||||
self.queue.cancel_task(self.__refilter)
|
||||
self.queue.push(self.__refilter, change_how, first=True)
|
||||
|
||||
def remove_system_track(self, playlist: Playlist, track: Track) -> bool:
|
||||
"""Remove a Track from a system Playlist."""
|
||||
return self.sql("""DELETE FROM system_tracks
|
||||
|
|
|
@ -149,7 +149,7 @@ class Table(Gtk.FilterListModel):
|
|||
filter: KeySet | None = None,
|
||||
queue: Queue | None = None, **kwargs):
|
||||
"""Set up our Table object."""
|
||||
super().__init__(sql=sql, rows=dict(), incremental=True,
|
||||
super().__init__(sql=sql, rows=dict(),
|
||||
store=store.SortedList(self.get_sort_key),
|
||||
filter=(filter if filter else KeySet()),
|
||||
queue=(queue if queue else Queue()), **kwargs)
|
||||
|
@ -227,7 +227,8 @@ class Table(Gtk.FilterListModel):
|
|||
def filter(self, glob: str | None, *, now: bool = False) -> None:
|
||||
"""Filter the displayed Rows."""
|
||||
if glob is not None:
|
||||
self.queue.push(self._filter_idle, glob, now=now)
|
||||
self.queue.cancel_task(self._filter_idle)
|
||||
self.queue.push(self._filter_idle, glob, now=now, first=True)
|
||||
else:
|
||||
self.get_filter().keys = None
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ gi.importlib.import_module("gi.repository.Gtk")
|
|||
gi.importlib.import_module("gi.repository.Gst").init(sys.argv)
|
||||
|
||||
DEBUG_STR = "-debug" if __debug__ else ""
|
||||
APPLICATION_ID = f"com.nowheycreamery.emmental{'-debug' if __debug__ else ''}"
|
||||
APPLICATION_ID = f"com.nowheycreamery.emmental{DEBUG_STR}"
|
||||
|
||||
CSS_FILE = pathlib.Path(__file__).parent / "emmental.css"
|
||||
CSS_PRIORITY = gi.repository.Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
"""Implement the MPRIS2 Specification."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from .. import gsetup
|
||||
from . import application
|
||||
from . import player
|
||||
|
||||
MPRIS2_ID = f"org.mpris.MediaPlayer2.emmental{'-debug' if __debug__ else ''}"
|
||||
MPRIS2_ID = f"org.mpris.MediaPlayer2.emmental{gsetup.DEBUG_STR}"
|
||||
|
||||
|
||||
class Connection(GObject.GObject):
|
||||
|
|
|
@ -8,6 +8,7 @@ from . import genre
|
|||
from . import library
|
||||
from . import playlist
|
||||
from . import section
|
||||
from ..action import ActionEntry
|
||||
from .. import db
|
||||
from .. import entry
|
||||
|
||||
|
@ -23,33 +24,41 @@ class Card(Gtk.Box):
|
|||
"""Set up the Sidebar widget."""
|
||||
super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL,
|
||||
sensitive=False, **kwargs)
|
||||
self._filter = entry.Filter("playlists")
|
||||
self._header = Gtk.CenterBox()
|
||||
self._filter = entry.Filter("playlists", hexpand=True)
|
||||
self._jump = Gtk.Button(icon_name="go-jump-symbolic",
|
||||
tooltip_text="scroll to current playlist")
|
||||
self._playlists = playlist.Section(self.sql.playlists)
|
||||
self._artists = artist.Section(self.sql.artists, self.sql.albums)
|
||||
self._genres = genre.Section(self.sql.genres)
|
||||
self._decades = decade.Section(self.sql.decades, self.sql.years)
|
||||
self._libraries = library.Section(self.sql.libraries)
|
||||
self._group = section.Group(sql)
|
||||
self._view = section.View(sql)
|
||||
|
||||
self.append(self._filter)
|
||||
self._header.set_center_widget(self._filter)
|
||||
self._header.set_end_widget(self._jump)
|
||||
self.append(self._header)
|
||||
|
||||
for sect in [self._playlists, self._artists, self._genres,
|
||||
self._decades, self._libraries]:
|
||||
self.append(sect)
|
||||
self._group.add(sect)
|
||||
self._view.add(sect)
|
||||
self.append(self._view)
|
||||
|
||||
self._group.bind_property("selected-playlist",
|
||||
self, "selected-playlist")
|
||||
self._view.bind_property("selected-playlist",
|
||||
self, "selected-playlist")
|
||||
self.bind_property("show-all-artists", self._artists, "show-all",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
|
||||
self._filter.connect("search-changed", self.__search_changed)
|
||||
self._jump.connect("clicked", self.__jump_to_playlist)
|
||||
self.sql.connect("table-loaded", self.__table_loaded)
|
||||
|
||||
self.add_css_class("background")
|
||||
self.add_css_class("linked")
|
||||
self._header.add_css_class("toolbar")
|
||||
self.add_css_class("card")
|
||||
|
||||
def __jump_to_playlist(self, jump: Gtk.Button) -> None:
|
||||
self.select_playlist(self.sql.active_playlist)
|
||||
|
||||
def __search_changed(self, entry: entry.Filter) -> None:
|
||||
self.sql.filter(entry.get_query())
|
||||
|
||||
|
@ -78,3 +87,21 @@ class Card(Gtk.Box):
|
|||
|
||||
section.active = True
|
||||
section.select_playlist(playlist)
|
||||
|
||||
@property
|
||||
def accelerators(self) -> list[ActionEntry]:
|
||||
"""Get a list of accelerators for the Sidebar."""
|
||||
return [ActionEntry("focus-search-playlist", self._filter.grab_focus,
|
||||
"<Control>question", enabled=(self, "sensitive")),
|
||||
ActionEntry("goto-active-playlist", self._jump.activate,
|
||||
"<Control><Alt>g", enabled=(self, "sensitive")),
|
||||
ActionEntry("goto-playlists", self._playlists.activate,
|
||||
"<Shift><Control>p", enabled=(self, "sensitive")),
|
||||
ActionEntry("goto-artists", self._artists.activate,
|
||||
"<Shift><Control>a", enabled=(self, "sensitive")),
|
||||
ActionEntry("goto-genres", self._genres.activate,
|
||||
"<Shift><Control>g", enabled=(self, "sensitive")),
|
||||
ActionEntry("goto-decades", self._decades.activate,
|
||||
"<Shift><Control>d", enabled=(self, "sensitive")),
|
||||
ActionEntry("goto-libraries", self._libraries.activate,
|
||||
"<Shift><Control>l", enabled=(self, "sensitive"))]
|
||||
|
|
|
@ -55,7 +55,7 @@ class Header(Gtk.Box):
|
|||
self.bind_property("reveal-widget", self._revealer, "child")
|
||||
self.bind_property("animation", self._revealer, "transition-type")
|
||||
|
||||
self._clicked.connect("released", self.__clicked)
|
||||
self._clicked.connect("released", self.activate)
|
||||
self.connect("notify::active", self.__notify_active)
|
||||
|
||||
self._box.append(self._icon)
|
||||
|
@ -70,12 +70,12 @@ class Header(Gtk.Box):
|
|||
self.append(self._overlay)
|
||||
self.append(self._revealer)
|
||||
|
||||
def __clicked(self, gesture: Gtk.GestureClick, n_press: int,
|
||||
x: int, y: int) -> None:
|
||||
self.active = True
|
||||
|
||||
def __notify_active(self, header, param) -> None:
|
||||
if self.active:
|
||||
self._arrow.set_state_flags(Gtk.StateFlags.CHECKED, False)
|
||||
else:
|
||||
self._arrow.unset_state_flags(Gtk.StateFlags.CHECKED)
|
||||
|
||||
def activate(self, *args) -> None:
|
||||
"""Activate the Header."""
|
||||
self.active = True
|
||||
|
|
|
@ -99,8 +99,8 @@ class Section(header.Header):
|
|||
"""Signal that the selected playlist has changed."""
|
||||
|
||||
|
||||
class Group(GObject.GObject):
|
||||
"""A group of sections."""
|
||||
class View(Gtk.Box):
|
||||
"""A widget for displaying a group of sections."""
|
||||
|
||||
sql = GObject.Property(type=db.Connection)
|
||||
current = GObject.Property(type=Section)
|
||||
|
@ -108,8 +108,8 @@ class Group(GObject.GObject):
|
|||
selected_playlist = GObject.Property(type=db.playlist.Playlist)
|
||||
|
||||
def __init__(self, sql: db.Connection):
|
||||
"""Initialize a Section Group."""
|
||||
super().__init__(sql=sql)
|
||||
"""Initialize a Section View."""
|
||||
super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL)
|
||||
self._sections = []
|
||||
|
||||
def __on_active(self, section: Section, param: GObject.ParamSpec) -> None:
|
||||
|
@ -145,6 +145,7 @@ class Group(GObject.GObject):
|
|||
def add(self, section: Section) -> None:
|
||||
"""Add a section to the group."""
|
||||
self._sections.append(section)
|
||||
self.append(section)
|
||||
section.connect("notify::active", self.__on_active)
|
||||
section.connect("playlist-activated", self.__playlist_activated)
|
||||
section.connect("playlist-selected", self.__playlist_selected)
|
||||
|
|
|
@ -51,6 +51,26 @@ class TestIdleQueue(unittest.TestCase):
|
|||
self.assertEqual(self.queue.total, 0)
|
||||
self.assertEqual(self.queue.progress, 0.0)
|
||||
|
||||
def test_cancel_task(self, mock_idle_add: unittest.mock.Mock,
|
||||
mock_source_removed: unittest.mock.Mock):
|
||||
"""Test canceling a specific task."""
|
||||
self.queue.push(1)
|
||||
self.queue.push(2)
|
||||
self.queue.push(1)
|
||||
|
||||
self.queue.cancel_task(1)
|
||||
self.assertListEqual(self.queue._tasks, [(2,)])
|
||||
self.assertEqual(self.queue.total, 3)
|
||||
self.assertAlmostEqual(self.queue.progress, 2 / 3)
|
||||
mock_source_removed.assert_not_called()
|
||||
|
||||
self.queue.cancel_task(2)
|
||||
self.assertListEqual(self.queue._tasks, [])
|
||||
self.assertIsNone(self.queue._idle_id)
|
||||
self.assertEqual(self.queue.total, 0)
|
||||
self.assertEqual(self.queue.progress, 0.0)
|
||||
mock_source_removed.assert_called_with(42)
|
||||
|
||||
def test_complete(self, mock_idle_add: unittest.mock.Mock,
|
||||
mock_source_removed: unittest.mock.Mock):
|
||||
"""Test completing queued tasks."""
|
||||
|
@ -119,6 +139,17 @@ class TestIdleQueue(unittest.TestCase):
|
|||
mock_idle_add.assert_not_called()
|
||||
func.assert_called_with(1)
|
||||
|
||||
def test_push_first(self, mock_idle_add: unittest.mock.Mock,
|
||||
mock_source_removed: unittest.mock.Mock):
|
||||
"""Test pushing an idle task with first=True."""
|
||||
self.queue.push(1)
|
||||
self.queue.push(0, first=True)
|
||||
self.assertListEqual(self.queue._tasks, [(0,), (1,)])
|
||||
self.queue.push(2, first=False)
|
||||
self.assertListEqual(self.queue._tasks, [(0,), (1,), (2,)])
|
||||
self.queue.push(3)
|
||||
self.assertListEqual(self.queue._tasks, [(0,), (1,), (2,), (3,)])
|
||||
|
||||
def test_push_many_enabled(self, mock_idle_add: unittest.mock.Mock,
|
||||
mock_source_removed: unittest.mock.Mock):
|
||||
"""Test adding several calls to one function at one time."""
|
||||
|
|
|
@ -21,6 +21,7 @@ class TestPlaylistRow(unittest.TestCase):
|
|||
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)
|
||||
|
||||
|
@ -81,7 +82,7 @@ class TestPlaylistRow(unittest.TestCase):
|
|||
table.get_filter())
|
||||
self.assertEqual(self.playlist.children.get_model(),
|
||||
self.playlist.child_set)
|
||||
self.assertTrue(self.playlist.children.get_incremental())
|
||||
self.assertFalse(self.playlist.children.get_incremental())
|
||||
|
||||
playlist2 = emmental.db.playlist.Playlist(table=self.table,
|
||||
propertyid=2, name="Plist2")
|
||||
|
@ -114,11 +115,16 @@ class TestPlaylistRow(unittest.TestCase):
|
|||
def test_add_child(self):
|
||||
"""Test adding a child playlist to the playlist."""
|
||||
table = emmental.db.table.Table(None)
|
||||
child = tests.util.table.MockRow(table=table, number=1)
|
||||
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(child)
|
||||
self.assertIn(child, self.playlist.child_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."""
|
||||
|
@ -179,12 +185,19 @@ class TestPlaylistRow(unittest.TestCase):
|
|||
def test_remove_child(self):
|
||||
"""Test removing a child playlist from the playlist."""
|
||||
table = emmental.db.table.Table(None)
|
||||
child = tests.util.table.MockRow(table=table, number=1)
|
||||
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.add_child(child)
|
||||
self.playlist.remove_child(child)
|
||||
self.assertFalse(child in self.playlist.child_set)
|
||||
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."""
|
||||
|
@ -403,6 +416,27 @@ class TestPlaylistTable(tests.util.TestCase):
|
|||
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)
|
||||
|
|
|
@ -233,7 +233,7 @@ class TestTable(tests.util.TestCase):
|
|||
self.assertEqual(self.table.get_model(), self.table.store)
|
||||
self.assertEqual(self.table.store.key_func, self.table.get_sort_key)
|
||||
self.assertDictEqual(self.table.rows, {})
|
||||
self.assertTrue(self.table.get_incremental())
|
||||
self.assertFalse(self.table.get_incremental())
|
||||
|
||||
filter2 = emmental.db.table.KeySet()
|
||||
queue2 = emmental.db.idle.Queue()
|
||||
|
@ -368,9 +368,13 @@ class TestTableFunctions(tests.util.TestCase):
|
|||
"""Test filtering Rows in the table."""
|
||||
for n in [1, 121, 212, 333]:
|
||||
self.table.create(number=n)
|
||||
self.table.queue.push(unittest.mock.Mock())
|
||||
|
||||
self.table.filter("*3*")
|
||||
self.assertIsNone(self.table.get_filter().keys)
|
||||
self.assertEqual(self.table.queue[0], (self.table._filter_idle, "*3*"))
|
||||
|
||||
self.table.filter("*2*")
|
||||
self.assertIsNone(self.table.get_filter().keys)
|
||||
self.assertEqual(self.table.queue[0], (self.table._filter_idle, "*2*"))
|
||||
|
||||
self.table.queue.complete()
|
||||
|
|
|
@ -165,3 +165,9 @@ class TestHeader(unittest.TestCase):
|
|||
self.assertTrue(self.header.active)
|
||||
flags = self.header._arrow.get_state_flags()
|
||||
self.assertTrue(flags & Gtk.StateFlags.CHECKED)
|
||||
|
||||
def test_activate(self):
|
||||
"""Test the activate() function."""
|
||||
self.assertFalse(self.header.active)
|
||||
self.header.activate()
|
||||
self.assertTrue(self.header.active)
|
||||
|
|
|
@ -4,7 +4,6 @@ import emmental.db
|
|||
import emmental.sidebar.section
|
||||
import tests.util
|
||||
import unittest.mock
|
||||
from gi.repository import GObject
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
@ -150,7 +149,7 @@ class TestGroup(tests.util.TestCase):
|
|||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
super().setUp()
|
||||
self.group = emmental.sidebar.section.Group(self.sql)
|
||||
self.view = emmental.sidebar.section.View(self.sql)
|
||||
self.row_type = emmental.sidebar.row.TreeRow
|
||||
self.section1 = emmental.sidebar.section.Section(self.sql.playlists,
|
||||
self.row_type)
|
||||
|
@ -161,35 +160,40 @@ class TestGroup(tests.util.TestCase):
|
|||
|
||||
def test_init(self):
|
||||
"""Test that the Group is set up properly."""
|
||||
self.assertIsInstance(self.group, GObject.GObject)
|
||||
self.assertListEqual(self.group._sections, [])
|
||||
self.assertEqual(self.group.sql, self.sql)
|
||||
self.assertIsInstance(self.view, Gtk.Box)
|
||||
self.assertListEqual(self.view._sections, [])
|
||||
self.assertEqual(self.view.sql, self.sql)
|
||||
self.assertEqual(self.view.get_orientation(),
|
||||
Gtk.Orientation.VERTICAL)
|
||||
|
||||
def test_add(self):
|
||||
"""Test adding sections to the Group."""
|
||||
self.group.add(self.section1)
|
||||
self.assertListEqual(self.group._sections, [self.section1])
|
||||
self.group.add(self.section2)
|
||||
self.assertListEqual(self.group._sections,
|
||||
self.view.add(self.section1)
|
||||
self.assertListEqual(self.view._sections, [self.section1])
|
||||
self.assertEqual(self.view.get_first_child(), self.section1)
|
||||
|
||||
self.view.add(self.section2)
|
||||
self.assertListEqual(self.view._sections,
|
||||
[self.section1, self.section2])
|
||||
self.assertEqual(self.section1.get_next_sibling(), self.section2)
|
||||
|
||||
def test_current(self):
|
||||
"""Test the current section property."""
|
||||
self.group.add(self.section1)
|
||||
self.group.add(self.section2)
|
||||
self.assertIsNone(self.group.current)
|
||||
self.view.add(self.section1)
|
||||
self.view.add(self.section2)
|
||||
self.assertIsNone(self.view.current)
|
||||
|
||||
self.section1.active = True
|
||||
self.assertEqual(self.group.current, self.section1)
|
||||
self.assertEqual(self.view.current, self.section1)
|
||||
|
||||
self.section2.active = True
|
||||
self.assertEqual(self.group.current, self.section2)
|
||||
self.assertEqual(self.view.current, self.section2)
|
||||
self.assertFalse(self.section1.active)
|
||||
|
||||
def test_animation(self):
|
||||
"""Test setting the section animation style."""
|
||||
self.group.add(self.section1)
|
||||
self.group.add(self.section2)
|
||||
self.view.add(self.section1)
|
||||
self.view.add(self.section2)
|
||||
|
||||
self.section1.active = True
|
||||
self.assertEqual(self.section1.animation,
|
||||
|
@ -201,8 +205,8 @@ class TestGroup(tests.util.TestCase):
|
|||
|
||||
def test_playlist_activated(self):
|
||||
"""Test responding to the section playlist-activated signal."""
|
||||
self.group.add(self.section1)
|
||||
self.group.add(self.section2)
|
||||
self.view.add(self.section1)
|
||||
self.view.add(self.section2)
|
||||
self.assertIsNone(self.sql.active_playlist)
|
||||
|
||||
playlist = self.sql.playlists.create("Test Playlist")
|
||||
|
@ -215,16 +219,16 @@ class TestGroup(tests.util.TestCase):
|
|||
|
||||
def test_selections(self):
|
||||
"""Test the selected section & playlist properties."""
|
||||
self.group.add(self.section1)
|
||||
self.group.add(self.section2)
|
||||
self.view.add(self.section1)
|
||||
self.view.add(self.section2)
|
||||
|
||||
self.assertIsNone(self.group.selected_section)
|
||||
self.assertIsNone(self.group.selected_playlist)
|
||||
self.assertIsNone(self.view.selected_section)
|
||||
self.assertIsNone(self.view.selected_playlist)
|
||||
|
||||
genre = self.sql.genres.create("Test Genre")
|
||||
self.section2.emit("playlist-selected", genre)
|
||||
self.assertEqual(self.group.selected_section, self.section2)
|
||||
self.assertEqual(self.group.selected_playlist, genre)
|
||||
self.assertEqual(self.view.selected_section, self.section2)
|
||||
self.assertEqual(self.view.selected_playlist, genre)
|
||||
|
||||
self.section2.active = True
|
||||
treerow = self.section2._selection.get_selected_item()
|
||||
|
|
|
@ -23,23 +23,46 @@ class TestSidebar(tests.util.TestCase):
|
|||
Gtk.Orientation.VERTICAL)
|
||||
self.assertFalse(self.sidebar.get_sensitive())
|
||||
|
||||
self.assertTrue(self.sidebar.has_css_class("background"))
|
||||
self.assertTrue(self.sidebar.has_css_class("linked"))
|
||||
self.assertTrue(self.sidebar.has_css_class("card"))
|
||||
|
||||
def test_header(self):
|
||||
"""Test the Sidebar header."""
|
||||
self.assertIsInstance(self.sidebar._header, Gtk.CenterBox)
|
||||
self.assertEqual(self.sidebar.get_first_child(), self.sidebar._header)
|
||||
self.assertTrue(self.sidebar._header.has_css_class("toolbar"))
|
||||
|
||||
def test_filter(self):
|
||||
"""Test the Sidebar filter entry."""
|
||||
self.assertIsInstance(self.sidebar._filter, emmental.entry.Filter)
|
||||
|
||||
self.assertEqual(self.sidebar.get_first_child(), self.sidebar._filter)
|
||||
self.assertEqual(self.sidebar._header.get_center_widget(),
|
||||
self.sidebar._filter)
|
||||
self.assertEqual(self.sidebar._filter.get_placeholder_text(),
|
||||
"type to filter playlists")
|
||||
self.assertTrue(self.sidebar._filter.get_hexpand())
|
||||
|
||||
with unittest.mock.patch.object(self.sql, "filter") as mock_filter:
|
||||
self.sidebar._filter.set_text("test text")
|
||||
self.sidebar._filter.emit("search-changed")
|
||||
mock_filter.assert_called_with("*test text*")
|
||||
|
||||
def test_jump(self):
|
||||
"""Test the jump button."""
|
||||
self.assertIsInstance(self.sidebar._jump, Gtk.Button)
|
||||
self.assertEqual(self.sidebar._header.get_end_widget(),
|
||||
self.sidebar._jump)
|
||||
|
||||
self.assertEqual(self.sidebar._jump.get_icon_name(),
|
||||
"go-jump-symbolic")
|
||||
self.assertEqual(self.sidebar._jump.get_tooltip_text(),
|
||||
"scroll to current playlist")
|
||||
|
||||
self.sql.playlists.load(now=True)
|
||||
with unittest.mock.patch.object(self.sidebar,
|
||||
"select_playlist") as mock_select:
|
||||
self.sidebar._jump.emit("clicked")
|
||||
mock_select.assert_called_with(self.sql.active_playlist)
|
||||
|
||||
def test_sensitivity_and_startup(self):
|
||||
"""Test setting the sidebar sensitivity when all tables have loaded."""
|
||||
tables = [t for t in self.sql.playlist_tables()]
|
||||
|
@ -73,15 +96,17 @@ class TestSidebar(tests.util.TestCase):
|
|||
self.assertIsNone(self.sidebar.selected_playlist)
|
||||
|
||||
playlist1 = self.sql.playlists.create("Playlist 1")
|
||||
self.sidebar._group.selected_playlist = playlist1
|
||||
self.sidebar._view.selected_playlist = playlist1
|
||||
self.assertEqual(self.sidebar.selected_playlist, playlist1)
|
||||
|
||||
def test_group(self):
|
||||
"""Test that sidebar sections are part of the same Group."""
|
||||
self.assertIsInstance(self.sidebar._group,
|
||||
emmental.sidebar.section.Group)
|
||||
def test_view(self):
|
||||
"""Test that sidebar sections are in the View."""
|
||||
self.assertIsInstance(self.sidebar._view,
|
||||
emmental.sidebar.section.View)
|
||||
self.assertEqual(self.sidebar._header.get_next_sibling(),
|
||||
self.sidebar._view)
|
||||
|
||||
self.assertListEqual(self.sidebar._group._sections,
|
||||
self.assertListEqual(self.sidebar._view._sections,
|
||||
[self.sidebar._playlists,
|
||||
self.sidebar._artists,
|
||||
self.sidebar._genres,
|
||||
|
@ -101,7 +126,7 @@ class TestSidebar(tests.util.TestCase):
|
|||
self.assertIsInstance(self.sidebar._libraries,
|
||||
emmental.sidebar.library.Section)
|
||||
|
||||
self.assertEqual(self.sidebar._filter.get_next_sibling(),
|
||||
self.assertEqual(self.sidebar._view.get_first_child(),
|
||||
self.sidebar._playlists)
|
||||
self.assertEqual(self.sidebar._playlists.get_next_sibling(),
|
||||
self.sidebar._artists)
|
||||
|
@ -161,3 +186,37 @@ class TestSidebar(tests.util.TestCase):
|
|||
self.sidebar.select_playlist(library)
|
||||
self.assertTrue(self.sidebar._libraries.active)
|
||||
self.assertEqual(self.sidebar.selected_playlist, library)
|
||||
|
||||
def test_accelerators(self):
|
||||
"""Check that the accelerators list is set up properly."""
|
||||
entries = [("focus-search-playlist", self.sidebar._filter.grab_focus,
|
||||
["<Control>question"]),
|
||||
("goto-active-playlist", self.sidebar._jump.activate,
|
||||
["<Control><Alt>g"]),
|
||||
("goto-playlists", self.sidebar._playlists.activate,
|
||||
["<Shift><Control>p"]),
|
||||
("goto-artists", self.sidebar._artists.activate,
|
||||
["<Shift><Control>a"]),
|
||||
("goto-genres", self.sidebar._genres.activate,
|
||||
["<Shift><Control>g"]),
|
||||
("goto-decades", self.sidebar._decades.activate,
|
||||
["<Shift><Control>d"]),
|
||||
("goto-libraries", self.sidebar._libraries.activate,
|
||||
["<Shift><Control>l"])]
|
||||
|
||||
accels = self.sidebar.accelerators
|
||||
self.assertIsInstance(accels, list)
|
||||
|
||||
for i, (name, func, accel) 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.assertListEqual(accels[i].accels, accel)
|
||||
|
||||
enabled = self.sidebar.get_sensitive()
|
||||
self.assertEqual(accels[i].enabled, enabled)
|
||||
self.sidebar.set_sensitive(not enabled)
|
||||
self.assertEqual(accels[i].enabled, not enabled)
|
||||
|
||||
self.assertEqual(len(accels), i + 1)
|
||||
|
|
|
@ -22,9 +22,9 @@ class TestEmmental(unittest.TestCase):
|
|||
"""Check that version constants have been set properly."""
|
||||
self.assertEqual(emmental.MAJOR_VERSION, 3)
|
||||
self.assertEqual(emmental.MINOR_VERSION, 0)
|
||||
self.assertEqual(emmental.MICRO_VERSION, 4)
|
||||
self.assertEqual(emmental.VERSION_NUMBER, "3.0.4")
|
||||
self.assertEqual(emmental.VERSION_STRING, "Emmental 3.0.4-debug")
|
||||
self.assertEqual(emmental.MICRO_VERSION, 5)
|
||||
self.assertEqual(emmental.VERSION_NUMBER, "3.0.5")
|
||||
self.assertEqual(emmental.VERSION_STRING, "Emmental 3.0.5-debug")
|
||||
|
||||
def test_application(self):
|
||||
"""Check that the application instance is initialized properly."""
|
||||
|
@ -63,7 +63,7 @@ class TestEmmental(unittest.TestCase):
|
|||
mock_startup.assert_called()
|
||||
mock_load.assert_called()
|
||||
mock_add_window.assert_called_with(self.application.win)
|
||||
mock_set_useragent.assert_called_with("emmental-debug", "3.0.4")
|
||||
mock_set_useragent.assert_called_with("emmental-debug", "3.0.5")
|
||||
|
||||
@unittest.mock.patch("sys.stdout")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.add_window")
|
||||
|
@ -205,6 +205,17 @@ class TestEmmental(unittest.TestCase):
|
|||
self.application.player = emmental.audio.Player()
|
||||
win = self.application.build_window()
|
||||
|
||||
for action, accel in [("app.focus-search-playlist",
|
||||
"<Control>question"),
|
||||
("app.goto-active-playlist", "<Control><Alt>g"),
|
||||
("app.goto-playlists", "<Shift><Control>p"),
|
||||
("app.goto-artists", "<Shift><Control>a"),
|
||||
("app.goto-genres", "<Shift><Control>g"),
|
||||
("app.goto-decades", "<Shift><Control>d"),
|
||||
("app.goto-libraries", "<Shift><Control>l")]:
|
||||
self.assertEqual(self.application.get_accels_for_action(action),
|
||||
[accel])
|
||||
|
||||
self.assertEqual(win.sidebar.sql, self.application.db)
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
|
|
Loading…
Reference in New Issue