Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/__tests__/skills-index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,40 @@ describe("SkillsIndex", () => {
expect(screen.getByRole("button", { name: "Load more" })).toBeTruthy();
});

it.each(["new", "featured", "official"] as const)(
"keeps the %s first page retryable after a temporary fetch failure",
async (tab) => {
searchMock = { tab };
vi.stubGlobal("IntersectionObserver", undefined);
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
try {
convexHttpMock.query
.mockRejectedValueOnce(new Error("temporary failure"))
.mockResolvedValueOnce({
page: [makeListResult("recovered-skill", "Recovered Skill")],
hasMore: false,
nextCursor: null,
});

render(<SkillsIndex />);
await act(async () => {});

expect(screen.queryByText("No skills found")).toBeNull();
expect(screen.getByRole("button", { name: "Load more" })).toBeTruthy();

await act(async () => {
fireEvent.click(screen.getByRole("button", { name: "Load more" }));
});

expect(convexHttpMock.query).toHaveBeenCalledTimes(2);
expect(screen.getByText("Recovered Skill")).toBeTruthy();
expect(screen.queryByRole("button", { name: "Load more" })).toBeNull();
} finally {
consoleErrorSpy.mockRestore();
}
},
);

it("keeps loading across empty filtered pages without flashing terminal states", async () => {
class IntersectionObserverMock {
observe = vi.fn();
Expand Down
4 changes: 2 additions & 2 deletions src/routes/skills/-useSkillsBrowseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,10 @@ export function useSkillsBrowseModel({
setListResults([]);
setTrendingState("unavailable");
}
// Reset to idle so the user can retry via "Load more"
// Keep canonical Trending's dedicated unavailable state; other pages remain retryable.
setListCursor(pageCursor);
setListAutoLoadPaused(Boolean(pageCursor));
setListStatus(pageCursor ? "idle" : "done");
setListStatus(catalogTab === "trending" && !pageCursor ? "done" : "idle");
}
},
[
Expand Down
Loading