fix(plex): a reshuffle survives a refresh landing mid-deal; the ↻ spins for a deal only
The ↻ intent was spent as its fetch set out, so a watched toggle's resync cancelling that fetch re-ran it as a re-read of the old draw. It now lasts until the latest deal answers. The button spun on every re-read of the same titles; it now follows the deal. Comments that said the Surprise draw is never refetched are brought in line.
This commit is contained in:
@@ -274,6 +274,52 @@ describe("PlexDiscover", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// A watched toggle's resync refreshes every rail, the Surprise draw included. Landing while a ↻
|
||||
// deal was in flight, it cancelled that fetch and re-ran it as a re-read: the old titles came back
|
||||
// and the ↻ did nothing. And the ↻ spins for a deal only, never for a re-read of the same titles.
|
||||
it("a refresh landing mid-deal deals again; only a deal spins the ↻", async () => {
|
||||
plexDiscover.mockResolvedValue(fullPayload);
|
||||
const answers: ((v: unknown) => void)[] = [];
|
||||
const rereads: ((v: unknown) => void)[] = [];
|
||||
plexSurprise.mockImplementation(
|
||||
(_s: string, _l: unknown, _f: unknown, _lang: string, o: { keep?: string[] }) =>
|
||||
plexSurprise.mock.calls.length === 1
|
||||
? Promise.resolve({ scope: "both", items: [card("sp1", "Old Pick")] })
|
||||
: new Promise((r) => (o.keep ? rereads : answers).push(r)),
|
||||
);
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
render(
|
||||
<QueryClientProvider client={qc}>
|
||||
<PlexDiscover
|
||||
scope="both"
|
||||
onOpenCard={noop}
|
||||
onRestartCard={() => {}}
|
||||
onInfoCard={noop}
|
||||
onGenre={noop}
|
||||
onSeeAllRecent={noop}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
await screen.findByText("Old Pick");
|
||||
const button = () => screen.getByRole("button", { name: "plex.discover.reshuffle" });
|
||||
act(() => void qc.invalidateQueries({ queryKey: qk.plexDiscover() }));
|
||||
await waitFor(() => expect(rereads).toHaveLength(1));
|
||||
expect(button().getAttribute("aria-busy")).toBeNull();
|
||||
await act(async () => rereads[0]?.({ scope: "both", items: [card("sp1", "Old Pick")] }));
|
||||
fireEvent.click(button());
|
||||
expect(button().getAttribute("aria-busy")).toBe("true");
|
||||
act(() => void qc.invalidateQueries({ queryKey: qk.plexDiscover() }));
|
||||
await waitFor(() => expect(answers).toHaveLength(2));
|
||||
const last = plexSurprise.mock.calls[plexSurprise.mock.calls.length - 1];
|
||||
expect(last?.[4]).toEqual({ exclude: ["sp1"] });
|
||||
// The cancelled deal's late answer neither lands nor stops the spinner of the one replacing it.
|
||||
await act(async () => answers[0]?.({ scope: "both", items: [card("sp9", "Stale Pick")] }));
|
||||
expect(button().getAttribute("aria-busy")).toBe("true");
|
||||
await act(async () => answers[1]?.({ scope: "both", items: [card("sp2", "New Pick")] }));
|
||||
await screen.findByText("New Pick");
|
||||
expect(button().getAttribute("aria-busy")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows the empty message when every rail is empty", async () => {
|
||||
plexDiscover.mockResolvedValue({
|
||||
scope: "both",
|
||||
@@ -298,8 +344,8 @@ describe("PlexDiscover", () => {
|
||||
});
|
||||
|
||||
it("puts a Surprise card back when its watched toggle fails", async () => {
|
||||
// The resync refetches the rails but not the Surprise draw (that would reshuffle it), so a
|
||||
// refused toggle stayed flipped there — the sibling of the collection page's fix.
|
||||
// The resync re-reads the Surprise draw too, but a refused toggle is put back at once rather than
|
||||
// left flipped until that read lands — the sibling of the collection page's fix.
|
||||
plexDiscover.mockResolvedValue({
|
||||
...fullPayload,
|
||||
continue_watching: [],
|
||||
|
||||
Reference in New Issue
Block a user