class TPlaylistsBrowser(TSearchBar):
    Bar = PlaylistsBrowser

    ANOTHER_SONG = AudioFile({
        "title": "lonely",
        "artist": "new artist",
        "~filename": dummy_path(u"/dev/urandom")
    })

    def setUp(self):
        # 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)

        self.lib = quodlibet.browsers.playlists.library = SongLibrary()
        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)

    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)
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)
Example #3
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(TSearchBar):
    Bar = PlaylistsBrowser

    ANOTHER_SONG = AudioFile({
        "title": "lonely",
        "artist": "new artist",
        "~filename": dummy_path(u"/dev/urandom")})

    def setUp(self):
        # 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)

        self.lib = quodlibet.browsers.playlists.library = SongLibrary()
        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)

    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)
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 TPlaylists(TSearchBar):
    Bar = PlaylistsBrowser
    ANOTHER_SONG = AudioFile({
        "title": "lonely",
        "artist": "new artist",
        "~filename": fsnative(u"/dev/urandom")})

    @classmethod
    def setUpClass(cls):
        # Only want to do this playlists setup once per test class...
        quodlibet.config.init()
        cls.lib = quodlibet.browsers.playlists.library = SongLibrary()
        cls.lib.librarian = SongLibrarian()
        all_songs = SONGS + [cls.ANOTHER_SONG]
        for af in all_songs:
            af.sanitize()
        cls.lib.add(all_songs)
        cls._create_temp_playlist_with("Big", SONGS)
        cls._create_temp_playlist_with("Small", [cls.ANOTHER_SONG])
        PlaylistsBrowser.init(cls.lib)

    @classmethod
    def _create_temp_playlist_with(cls, name, songs):
        pl = Playlist.new(PLAYLISTS, name, cls.lib)
        pl.extend(songs)
        pl.write()

    def setUp(self):
        self.bar = PlaylistsBrowser(self.lib)
        self.bar.connect('songs-selected', self._expected)
        self.bar._select_playlist(self.bar.playlists()[0])
        self.expected = 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 tearDown(self):
        self.bar.destroy()
        self.lib.destroy()

    @classmethod
    def tearDownClass(cls):
        quodlibet.config.quit()
class TPlaylists(TSearchBar):
    Bar = PlaylistsBrowser
    ANOTHER_SONG = AudioFile({
        "title": "lonely",
        "artist": "new artist",
        "~filename": fsnative(u"/dev/urandom")})

    @classmethod
    def setUpClass(cls):
        # Only want to do this playlists setup once per test class...
        quodlibet.config.init()
        cls.lib = quodlibet.browsers.playlists.library = SongLibrary()
        cls.lib.librarian = SongLibrarian()
        all_songs = SONGS + [cls.ANOTHER_SONG]
        for af in all_songs:
            af.sanitize()
        cls.lib.add(all_songs)
        cls._create_temp_playlist_with("Big", SONGS)
        cls._create_temp_playlist_with("Small", [cls.ANOTHER_SONG])
        PlaylistsBrowser.init(cls.lib)

    @classmethod
    def _create_temp_playlist_with(cls, name, songs):
        pl = Playlist.new(PLAYLISTS, name, cls.lib)
        pl.extend(songs)
        pl.write()

    def setUp(self):
        self.bar = PlaylistsBrowser(self.lib)
        self.bar.connect('songs-selected', self._expected)
        self.bar._select_playlist(self.bar.playlists()[0])
        self.expected = 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 tearDown(self):
        self.bar.destroy()
        self.lib.destroy()

    @classmethod
    def tearDownClass(cls):
        quodlibet.config.quit()
class TPlaylists(TSearchBar):
    Bar = PlaylistsBrowser

    ANOTHER_SONG = AudioFile({
        "title": "lonely",
        "artist": "new artist",
        "~filename": dummy_path(u"/dev/urandom")})

    def setUp(self):
        try:
            shutil.rmtree(PLAYLISTS)
        except OSError:
            pass

        mkdir(PLAYLISTS)

        self.lib = quodlibet.browsers.playlists.library = SongLibrary()
        self.lib.librarian = SongLibrarian()
        all_songs = SONGS + [self.ANOTHER_SONG]
        for af in all_songs:
            af.sanitize()
        self.lib.add(all_songs)

        pl = Playlist.new(PLAYLISTS, "Big", self.lib)
        pl.extend(SONGS)
        pl.write()

        pl = Playlist.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)

    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")