fix(plex): a collection's failed watched toggle is put back the way it was
C1 moved the optimistic flip onto the key the page reads — and so made visible that nothing corrects it: the resync only invalidated `plex-library`, so a failed toggle stayed "watched" beside its own error toast, and the collection's other cached sort orders stayed stale after a good one. The resync now takes this page's `detail` prefix too, as the Discover and person pages do for theirs. The C1 test holds the request open, so it still pins the optimistic write on its own.
This commit is contained in:
@@ -38,7 +38,13 @@ vi.mock("../lib/notifications", async (importOriginal) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
import PlexCollectionView from "./PlexCollectionView";
|
import PlexCollectionView from "./PlexCollectionView";
|
||||||
import { api, type PlexCard, type PlexCollectionDetail, type PlexShowDetail } from "../lib/api";
|
import {
|
||||||
|
api,
|
||||||
|
HttpError,
|
||||||
|
type PlexCard,
|
||||||
|
type PlexCollectionDetail,
|
||||||
|
type PlexShowDetail,
|
||||||
|
} from "../lib/api";
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -135,12 +141,24 @@ describe("PlexCollectionView — after the sort joined the query key", () => {
|
|||||||
it("flips a poster's watched state on the page it was clicked on", async () => {
|
it("flips a poster's watched state on the page it was clicked on", async () => {
|
||||||
// The optimistic write kept the key's OLD spelling — an entry nothing reads — and the resync
|
// The optimistic write kept the key's OLD spelling — an entry nothing reads — and the resync
|
||||||
// only touches the library root, so the click did nothing visible until a reload.
|
// only touches the library root, so the click did nothing visible until a reload.
|
||||||
vi.spyOn(api, "plexSetState").mockResolvedValue(undefined as never);
|
// The request never answers, so the resync never runs: only the optimistic write can flip it.
|
||||||
|
vi.spyOn(api, "plexSetState").mockReturnValue(new Promise(() => {}));
|
||||||
mount([card("movie1", "movie")]);
|
mount([card("movie1", "movie")]);
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "plex.markWatched" }));
|
fireEvent.click(await screen.findByRole("button", { name: "plex.markWatched" }));
|
||||||
expect(await screen.findByRole("button", { name: "plex.markUnwatched" })).toBeTruthy();
|
expect(await screen.findByRole("button", { name: "plex.markUnwatched" })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("puts a toggle that failed back the way it was", async () => {
|
||||||
|
// With the flip landing on the entry the page reads, the resync is what corrects it — and it only
|
||||||
|
// touched the library root, so a failed toggle stayed "watched" beside its own error toast.
|
||||||
|
vi.spyOn(api, "plexSetState").mockRejectedValue(new HttpError(422, "nope"));
|
||||||
|
mount([card("movie1", "movie")]);
|
||||||
|
fireEvent.click(await screen.findByRole("button", { name: "plex.markWatched" }));
|
||||||
|
await waitFor(() => expect(api.plexCollection).toHaveBeenCalledTimes(2));
|
||||||
|
expect(await screen.findByRole("button", { name: "plex.markWatched" })).toBeTruthy();
|
||||||
|
expect(screen.queryByRole("button", { name: "plex.markUnwatched" })).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps the page, and the open Sort panel, on screen while the new order loads", async () => {
|
it("keeps the page, and the open Sort panel, on screen while the new order loads", async () => {
|
||||||
// Every sort change was a key with no data yet: the whole page — the control and its open panel
|
// Every sort change was a key with no data yet: the whole page — the control and its open panel
|
||||||
// included — was swapped for a spinner, so key and direction could not be set in one visit.
|
// included — was swapped for a spinner, so key and direction could not be set in one visit.
|
||||||
|
|||||||
@@ -165,7 +165,14 @@ export default function PlexCollectionView({
|
|||||||
: old,
|
: old,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
resync: () => qc.invalidateQueries({ queryKey: qk.plexLibrary() }),
|
// This page's OWN key too, not just the library's: the flip above lands on the entry the page
|
||||||
|
// reads, and resync is what corrects it to the truth when the toggle fails — invalidating only
|
||||||
|
// `plex-library` left a failed toggle flipped on screen. The prefix (no sort in it) also
|
||||||
|
// refreshes the other sort orders of this collection cached behind this one.
|
||||||
|
resync: () => {
|
||||||
|
qc.invalidateQueries({ queryKey: qk.plexLibrary() });
|
||||||
|
qc.invalidateQueries({ queryKey: qk.plexCollections("detail", collectionId) });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function doRename() {
|
async function doRename() {
|
||||||
|
|||||||
Reference in New Issue
Block a user