fix(playlists): an Undo puts a row back after its old neighbour, not at a stale index

A raw index recorded at removal drifted once anything changed before the Undo.
Both lists now read the list fresh and re-insert each row after the row that
preceded it (lib/reinsert). A failed watched toggle in a Plex playlist also
puts the row's state back: its local copy was never re-seeded.
This commit is contained in:
2026-09-27 14:02:15 +02:00
parent b456232d00
commit de62035889
9 changed files with 225 additions and 32 deletions
+20 -4
View File
@@ -449,9 +449,10 @@ describe("Playlists — removing a row, and undoing it", () => {
});
it("puts it back where it stood in the stored order, whatever the view on screen", async () => {
vi.spyOn(api, "removeFromPlaylist").mockResolvedValue(undefined as never);
const lists = { 1: [vid("A", 0), vid("B", 0), vid("C", 0)] };
serverRemoves(lists);
vi.spyOn(api, "addToPlaylist").mockResolvedValue(undefined as never);
mount({ 1: [vid("A", 0), vid("B", 0), vid("C", 0)] });
mount(lists);
await screen.findByRole("button", { name: "card.play — Video C" });
// Sort the VIEW by title, descending: C, B, A on screen — the stored order is still A, B, C.
fireEvent.click(screen.getByRole("button", { name: /playlists\.colTitle/ }));
@@ -463,10 +464,25 @@ describe("Playlists — removing a row, and undoing it", () => {
await waitFor(() => expect(api.addToPlaylist).toHaveBeenCalledWith(1, "A", 0));
});
// E28 review C4: the stored index drifts when the list changes before the Undo (a row added at
// the top in another tab) — the row goes back after its old neighbour instead.
it("puts it back after its neighbour when the list changed before the Undo", async () => {
const lists = { 1: [vid("A", 0), vid("B", 0), vid("C", 0)] };
serverRemoves(lists);
vi.spyOn(api, "addToPlaylist").mockResolvedValue(undefined as never);
mount(lists);
await rowMenu("Video B", "playlists.removeItem");
await waitFor(() => expect(lastToast()?.action).toBeTruthy());
lists[1] = [vid("X", 0), ...lists[1]!];
act(() => lastToast().action!.onClick());
await waitFor(() => expect(api.addToPlaylist).toHaveBeenCalledWith(1, "B", 2));
});
it("says so when the Undo fails", async () => {
vi.spyOn(api, "removeFromPlaylist").mockResolvedValue(undefined as never);
const lists = { 1: [vid("A", 0), vid("B", 0)] };
serverRemoves(lists);
vi.spyOn(api, "addToPlaylist").mockRejectedValue(new TypeError("Failed to fetch"));
mount({ 1: [vid("A", 0), vid("B", 0)] });
mount(lists);
await rowMenu("Video A", "playlists.removeItem");
await waitFor(() => expect(lastToast()?.action).toBeTruthy());
act(() => lastToast().action!.onClick());