diff --git a/frontend/src/modules/feed/Feed.tsx b/frontend/src/modules/feed/Feed.tsx index b6651642..a24f3650 100644 --- a/frontend/src/modules/feed/Feed.tsx +++ b/frontend/src/modules/feed/Feed.tsx @@ -624,7 +624,16 @@ export default function Feed({ {ytQuery.isLoading ? (
{t("feed.yt.searching")}
) : ytError && !ytItems.length ? ( -
{ytError}
+ // A first page that failed is said as the error it is, with Retry where trying again can + // help (YouTube busy or unreachable): Search YouTube again with the same term reads the + // failure from the cache and sends nothing. + void ytQuery.refetch()} + > + {ytError} + ) : ytItems.length === 0 ? (
{t("feed.yt.noResults")}
) : ( diff --git a/frontend/src/modules/feed/Feed.ytLoadMore.dom.test.tsx b/frontend/src/modules/feed/Feed.ytLoadMore.dom.test.tsx index 2eb865da..0686a889 100644 --- a/frontend/src/modules/feed/Feed.ytLoadMore.dom.test.tsx +++ b/frontend/src/modules/feed/Feed.ytLoadMore.dom.test.tsx @@ -146,6 +146,38 @@ describe("Feed — a live search's Load more that fails", () => { }); }); +describe("Feed — a live search whose first page fails", () => { + // Search YouTube again with the same term reads the cached failure: Retry is the way to ask again. + it("offers Retry where trying again can help, and reads the page on it", async () => { + const search = vi + .spyOn(api, "searchYoutube") + .mockRejectedValueOnce( + new HttpError(429, "Too many searches", { code: "refused", reason: "search_busy" }), + ) + .mockResolvedValueOnce({ items: [hit("a")], next_cursor: null, source: "scrape" } as never); + mount(); + expect(await screen.findByText("Too many searches")).toBeTruthy(); + fireEvent.click(screen.getByRole("button", { name: "common.retry" })); + await waitFor(() => + expect(screen.getByRole("list", { name: "results" }).textContent).toBe("hit a (new)"), + ); + expect(search).toHaveBeenCalledTimes(2); + }); + + it("offers no Retry after a refusal a retry cannot answer", async () => { + vi.spyOn(api, "searchYoutube").mockRejectedValue( + new HttpError(429, "Today's limit", { + code: "refused", + reason: "search_daily_limit", + limit: 2, + }), + ); + mount(); + expect(await screen.findByText("Today's limit")).toBeTruthy(); + expect(screen.queryByRole("button", { name: "common.retry" })).toBeNull(); + }); +}); + describe("Feed — a live search's Load more on its way", () => { // Marked in this session, then changed back elsewhere: a search read after the mark carries the // server's word.