class TPlaylistsBrowser(TestCase): Bar = PlaylistsBrowser ANOTHER_SONG = AudioFile({ "title": "lonely", "artist": "new artist", "~filename": dummy_path(u"/dev/urandom") }) def setUp(self): self.success = False # Testing locally is VERY dangerous without this... self.assertTrue(_TEMP_DIR in PLAYLISTS or os.name == "nt", msg="Failing, don't want to delete %s" % PLAYLISTS) try: shutil.rmtree(PLAYLISTS) except OSError: pass mkdir(PLAYLISTS) init_fake_app() self.lib = quodlibet.browsers.playlists.library = SongFileLibrary() self.lib.librarian = SongLibrarian() all_songs = SONGS + [self.ANOTHER_SONG] for af in all_songs: af.sanitize() self.lib.add(all_songs) self.big = pl = FileBackedPlaylist.new(PLAYLISTS, "Big", self.lib) pl.extend(SONGS) pl.write() self.small = pl = FileBackedPlaylist.new(PLAYLISTS, "Small", self.lib) pl.extend([self.ANOTHER_SONG]) pl.write() PlaylistsBrowser.init(self.lib) self.bar = PlaylistsBrowser(self.lib) self.bar.connect('songs-selected', self._expected) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = None def tearDown(self): self.bar.destroy() self.lib.destroy() shutil.rmtree(PLAYLISTS) PlaylistsBrowser.deinit(self.lib) destroy_fake_app() def _expected(self, bar, songs, sort): songs.sort() if self.expected is not None: self.failUnlessEqual(self.expected, songs) self.success = True def _do(self): while Gtk.events_pending(): Gtk.main_iteration() self.failUnless(self.success or self.expected is None) def test_saverestore(self): # Flush previous signals, etc. Hmm. self.expected = None self._do() self.expected = [SONGS[0]] self.bar.filter_text("title = %s" % SONGS[0]["title"]) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = [SONGS[0]] self._do() self.bar.save() self.bar.filter_text("") self.expected = list(sorted(SONGS)) self._do() self.bar.restore() self.bar.activate() self.expected = [SONGS[0]] self._do() def test_active_filter_playlists(self): self.bar._select_playlist(self.bar.playlists()[1]) # Second playlist should not have any of `SONGS` self.assertFalse(self.bar.active_filter(SONGS[0])) # But it should have `ANOTHER_SONG` self.assertTrue(self.bar.active_filter(self.ANOTHER_SONG), msg="Couldn't find song from second playlist") # ... and setting a reasonable filter on that song should match still self.bar.filter_text("lonely") self.assertTrue(self.bar.active_filter(self.ANOTHER_SONG), msg="Couldn't find song from second playlist with " "filter of 'lonely'") # ...unless it doesn't match that song self.bar.filter_text("piman") self.assertFalse(self.bar.active_filter(self.ANOTHER_SONG), msg="Shouldn't have matched 'piman' on second list") def test_rename(self): self.assertEquals(self.bar.playlists()[1], self.small) self.bar._rename(0, "zBig") self.assertEquals(self.bar.playlists()[0], self.small) self.assertEquals(self.bar.playlists()[1].name, "zBig") def test_default_display_pattern(self): pattern_text = self.bar.display_pattern_text self.failUnlessEqual(pattern_text, DEFAULT_PATTERN_TEXT) self.failUnless("<~name>" in pattern_text) def test_drag_data_get(self): b = self.bar song = AudioFile() song["~filename"] = fsnative(u"foo") sel = MockSelData() qltk.selection_set_songs(sel, [song]) b._drag_data_get(None, None, sel, DND_QL, None) def test_deletion(self): def a_delete_event(): ev = Gdk.Event() ev.type = Gdk.EventType.KEY_PRESS ev.keyval, accel_mod = Gtk.accelerator_parse("Delete") ev.state = Gtk.accelerator_get_default_mod_mask() & accel_mod return ev b = self.bar self._fake_browser_pack(b) event = a_delete_event() # This is selected in setUp() first_pl = b.playlists()[0] app.window.songlist.set_songs(first_pl) app.window.songlist.select_by_func(lambda x: True, scroll=False, one=True) original_length = len(first_pl) ret = b.key_pressed(event) self.failUnless(ret, msg="Didn't simulate a delete keypress") self.failUnlessEqual(len(first_pl), original_length - 1) def test_import(self): pl_name = u"_€3 œufs à Noël" pl = FileBackedPlaylist(_TEMP_DIR, pl_name, None) pl.extend(SONGS) pl.write() new_fn = os.path.splitext(text2fsn(pl.name))[0] + '.m3u' new_path = os.path.join(pl.dir, new_fn) os.rename(pl.filename, new_path) added = self.bar._import_playlists([new_path], self.lib) self.failUnlessEqual(added, 1, msg="Failed to add '%s'" % new_path) os.unlink(new_path) pls = self.bar.playlists() self.failUnlessEqual(len(pls), 3) # Leading underscore makes it always the last entry imported = pls[-1] self.failUnlessEqual(imported.name, pl_name) def fns(songs): return [song('~filename') for song in songs] self.failUnlessEqual(fns(imported.songs), fns(pl.songs)) @staticmethod def _fake_browser_pack(b): app.window.get_child().pack_start(b, True, True, 0)
class TPlaylistsBrowser(TestCase): Bar = PlaylistsBrowser ANOTHER_SONG = AudioFile({ "title": "lonely", "artist": "new artist", "~filename": dummy_path(u"/dev/urandom") }) ALL_SONGS = SONGS + [ANOTHER_SONG] def setUp(self): self.success = False # Testing locally is VERY dangerous without this... self.assertTrue(_TEMP_DIR in _DEFAULT_PLAYLIST_DIR or os.name == "nt", msg="Failing, don't want to delete %s" % _DEFAULT_PLAYLIST_DIR) try: shutil.rmtree(_DEFAULT_PLAYLIST_DIR) except OSError: pass mkdir(_DEFAULT_PLAYLIST_DIR) init_fake_app() self.lib = quodlibet.browsers.playlists.library = SongFileLibrary() self.lib.librarian = SongLibrarian() for af in self.ALL_SONGS: af.sanitize() self.lib.add(self.ALL_SONGS) self.big = pl = FileBackedPlaylist.new(_DEFAULT_PLAYLIST_DIR, "Big", self.lib, self.lib.playlists) pl.extend(SONGS) pl.write() self.small = pl = XSPFBackedPlaylist.new(_DEFAULT_PLAYLIST_DIR, "Small", self.lib, self.lib.playlists) pl.extend([self.ANOTHER_SONG]) pl.write() PlaylistsBrowser.init(self.lib) self.bar = PlaylistsBrowser(self.lib, self.MockConfirmerAccepting) self.bar.connect('songs-selected', self._expected) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = None # Uses the declining confirmer. self.bar_decline = PlaylistsBrowser(self.lib, self.MockConfirmerDeclining) self.bar_decline.connect('songs-selected', self._expected_decline) self.bar_decline._select_playlist(self.bar_decline.playlists()[0]) # Note that _do() uses self.expected, but _do() is not called by the # testcase for declining the prompt. Tests fail with a shared expected. self.expected_decline = None def tearDown(self): self.small.delete() self.big.delete() self.bar.destroy() self.lib.destroy() shutil.rmtree(_DEFAULT_PLAYLIST_DIR) destroy_fake_app() def _expected(self, bar, songs, sort): songs.sort() if self.expected is not None: self.failUnlessEqual(self.expected, songs) self.success = True def _expected_decline(self, bar, songs, sort): songs.sort() if self.expected_decline is not None: self.failUnlessEqual(self.expected_decline, songs) self.success = True def _do(self): run_gtk_loop() self.failUnless(self.success or self.expected is None) def test_saverestore(self): # Flush previous signals, etc. Hmm. self.expected = None self._do() self.expected = [SONGS[0]] self.bar.filter_text("title = %s" % SONGS[0]["title"]) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = [SONGS[0]] self._do() self.bar.save() self.bar.filter_text("") self.expected = list(sorted(SONGS)) self._do() self.bar.restore() self.bar.activate() self.expected = [SONGS[0]] self._do() def test_active_filter_playlists(self): self.bar._select_playlist(self.bar.playlists()[1]) # Second playlist should not have any of `SONGS` self.assertFalse(self.bar.active_filter(SONGS[0])) # But it should have `ANOTHER_SONG` self.assertTrue(self.bar.active_filter(self.ANOTHER_SONG), msg="Couldn't find song from second playlist") # ... and setting a reasonable filter on that song should match still self.bar.filter_text("lonely") self.assertTrue(self.bar.active_filter(self.ANOTHER_SONG), msg="Couldn't find song from second playlist with " "filter of 'lonely'") # ...unless it doesn't match that song self.bar.filter_text("piman") self.assertFalse(self.bar.active_filter(self.ANOTHER_SONG), msg="Shouldn't have matched 'piman' on second list") def test_rename(self): self.assertEquals(self.bar.playlists()[1], self.small) self.bar._rename(0, "zBig") self.assertEquals(self.bar.playlists()[0], self.small) self.assertEquals(self.bar.playlists()[1].name, "zBig") def test_default_display_pattern(self): pattern_text = self.bar.display_pattern_text self.failUnlessEqual(pattern_text, DEFAULT_PATTERN_TEXT) self.failUnless("<~name>" in pattern_text) def test_drag_data_get(self): b = self.bar song = AudioFile() song["~filename"] = fsnative(u"foo") sel = MockSelData() qltk.selection_set_songs(sel, [song]) b._drag_data_get(None, None, sel, DND_QL, None) def test_songs_deletion(self): b = self.bar self._fake_browser_pack(b) event = self.a_delete_event() # This is selected in setUp() first_pl = b.playlists()[0] app.window.songlist.set_songs(first_pl) app.window.songlist.select_by_func(lambda x: True, scroll=False, one=True) original_length = len(first_pl) ret = b.key_pressed(event) self.failUnless(ret, msg="Didn't simulate a delete keypress") self.failUnlessEqual(len(first_pl), original_length - 1) def test_playlist_deletion_ACCEPT(self): b = self.bar orig_length = len(b.playlists()) event = self.a_delete_event() first_pl = b.playlists()[0] second_pl = b.playlists()[1] b._select_playlist(first_pl) ret = b._PlaylistsBrowser__key_pressed(b, event) self.failUnless(ret, msg="Didn't simulate a delete keypress") self.failUnlessEqual(len(b.playlists()), orig_length - 1) self.failUnlessEqual(b.playlists()[0], second_pl) def test_playlist_deletion_CANCEL(self): b = self.bar_decline orig_length = len(b.playlists()) event = self.a_delete_event() first_pl = b.playlists()[0] second_pl = b.playlists()[1] b._select_playlist(first_pl) ret = b._PlaylistsBrowser__key_pressed(b, event) self.failUnless(ret, msg="Didn't simulate a delete keypress") self.failUnlessEqual(len(b.playlists()), orig_length) self.failUnlessEqual(b.playlists()[0], first_pl) self.failUnlessEqual(b.playlists()[1], second_pl) def test_import(self): def fns(songs): return [song('~filename') for song in songs] pl_lib = self.bar.pl_lib assert len( self.bar.playlists()) == 2, "Should start with two playlists" assert len(pl_lib) == 2, f"Started with {pl_lib.keys()}" pl_name = "_€3 œufs à Noël" pl_path = Path(_TEMP_DIR) / (pl_name + ".m3u") with open(pl_path, "wb") as f: for fn in fns(SONGS): f.write(fsn2bytes(fn, "utf-8") + b"\n") pls_added, songs_added = self.bar._import_playlists([str(pl_path)]) assert pls_added == 1, f"Failed to add {pl_path}" assert len(self.bar.songs_lib) == len(self.ALL_SONGS) assert songs_added == 0, "Why did we add existing songs?" assert len( pl_lib) == 3, f"Got PLs: \n{', '.join(str(pl) for pl in pl_lib)}" pls = self.bar.playlists() assert len( pls) == 3, f"Got PL rows: {', '.join(str(pl) for pl in pls)}" # Leading underscore makes it always the last entry imported = pls[-1] self.failUnlessEqual(fns(imported.songs), fns(SONGS)) @staticmethod def a_delete_event(): ev = Gdk.Event() ev.type = Gdk.EventType.KEY_PRESS ev.keyval, accel_mod = Gtk.accelerator_parse("Delete") ev.state = Gtk.accelerator_get_default_mod_mask() & accel_mod return ev @staticmethod def _fake_browser_pack(b): app.window.get_child().pack_start(b, True, True, 0) class MockConfirmerAccepting: RESPONSE_INVOKE = Gtk.ResponseType.YES def __init__(self, *args): pass def run(self, *args): return self.RESPONSE_INVOKE class MockConfirmerDeclining: RESPONSE_INVOKE = Gtk.ResponseType.YES def __init__(self, *args): pass def run(self, *args): return Gtk.ResponseType.CANCEL
class TPlaylistsBrowser(TestCase): Bar = PlaylistsBrowser ANOTHER_SONG = AudioFile({ "title": "lonely", "artist": "new artist", "~filename": dummy_path(u"/dev/urandom")}) def setUp(self): self.success = False # Testing locally is VERY dangerous without this... self.assertTrue(_TEMP_DIR in PLAYLISTS or os.name == "nt", msg="Failing, don't want to delete %s" % PLAYLISTS) try: shutil.rmtree(PLAYLISTS) except OSError: pass mkdir(PLAYLISTS) init_fake_app() self.lib = quodlibet.browsers.playlists.library = SongFileLibrary() self.lib.librarian = SongLibrarian() all_songs = SONGS + [self.ANOTHER_SONG] for af in all_songs: af.sanitize() self.lib.add(all_songs) self.big = pl = FileBackedPlaylist.new(PLAYLISTS, "Big", self.lib) pl.extend(SONGS) pl.write() self.small = pl = FileBackedPlaylist.new(PLAYLISTS, "Small", self.lib) pl.extend([self.ANOTHER_SONG]) pl.write() PlaylistsBrowser.init(self.lib) self.bar = PlaylistsBrowser(self.lib) self.bar.connect('songs-selected', self._expected) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = None def tearDown(self): self.bar.destroy() self.lib.destroy() shutil.rmtree(PLAYLISTS) PlaylistsBrowser.deinit(self.lib) destroy_fake_app() def _expected(self, bar, songs, sort): songs.sort() if self.expected is not None: self.failUnlessEqual(self.expected, songs) self.success = True def _do(self): while Gtk.events_pending(): Gtk.main_iteration() self.failUnless(self.success or self.expected is None) def test_saverestore(self): # Flush previous signals, etc. Hmm. self.expected = None self._do() self.expected = [SONGS[0]] self.bar.filter_text("title = %s" % SONGS[0]["title"]) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = [SONGS[0]] self._do() self.bar.save() self.bar.filter_text("") self.expected = list(sorted(SONGS)) self._do() self.bar.restore() self.bar.activate() self.expected = [SONGS[0]] self._do() def test_active_filter_playlists(self): self.bar._select_playlist(self.bar.playlists()[1]) # Second playlist should not have any of `SONGS` self.assertFalse(self.bar.active_filter(SONGS[0])) # But it should have `ANOTHER_SONG` self.assertTrue(self.bar.active_filter(self.ANOTHER_SONG), msg="Couldn't find song from second playlist") # ... and setting a reasonable filter on that song should match still self.bar.filter_text("lonely") self.assertTrue(self.bar.active_filter(self.ANOTHER_SONG), msg="Couldn't find song from second playlist with " "filter of 'lonely'") # ...unless it doesn't match that song self.bar.filter_text("piman") self.assertFalse(self.bar.active_filter(self.ANOTHER_SONG), msg="Shouldn't have matched 'piman' on second list") def test_rename(self): self.assertEquals(self.bar.playlists()[1], self.small) self.bar._rename(0, "zBig") self.assertEquals(self.bar.playlists()[0], self.small) self.assertEquals(self.bar.playlists()[1].name, "zBig") def test_default_display_pattern(self): pattern_text = self.bar.display_pattern_text self.failUnlessEqual(pattern_text, DEFAULT_PATTERN_TEXT) self.failUnless("<~name>" in pattern_text) def test_drag_data_get(self): b = self.bar song = AudioFile() song["~filename"] = fsnative(u"foo") sel = MockSelData() qltk.selection_set_songs(sel, [song]) b._drag_data_get(None, None, sel, DND_QL, None) def test_deletion(self): def a_delete_event(): ev = Gdk.Event() ev.type = Gdk.EventType.KEY_PRESS ev.keyval, accel_mod = Gtk.accelerator_parse("Delete") ev.state = Gtk.accelerator_get_default_mod_mask() & accel_mod return ev b = self.bar self._fake_browser_pack(b) event = a_delete_event() # This is selected in setUp() first_pl = b.playlists()[0] app.window.songlist.set_songs(first_pl) app.window.songlist.select_by_func(lambda x: True, scroll=False, one=True) original_length = len(first_pl) ret = b.key_pressed(event) self.failUnless(ret, msg="Didn't simulate a delete keypress") self.failUnlessEqual(len(first_pl), original_length - 1) def test_import(self): pl_name = u"_€3 œufs à Noël" pl = FileBackedPlaylist(_TEMP_DIR, pl_name, None) pl.extend(SONGS) pl.write() new_fn = os.path.splitext(text2fsn(pl.name))[0] + '.m3u' new_path = os.path.join(pl.dir, new_fn) os.rename(pl.filename, new_path) added = self.bar._import_playlists([new_path], self.lib) self.failUnlessEqual(added, 1, msg="Failed to add '%s'" % new_path) os.unlink(new_path) pls = self.bar.playlists() self.failUnlessEqual(len(pls), 3) # Leading underscore makes it always the last entry imported = pls[-1] self.failUnlessEqual(imported.name, pl_name) def fns(songs): return [song('~filename') for song in songs] self.failUnlessEqual(fns(imported.songs), fns(pl.songs)) @staticmethod def _fake_browser_pack(b): app.window.get_child().pack_start(b, True, True, 0)