From e7d18dd4202e729a171d3c2d2b01043660c86a44 Mon Sep 17 00:00:00 2001 From: jdluu Date: Sat, 29 Aug 2026 13:22:05 -0700 Subject: [PATCH] refactor: surface snapshot load failures Closes #107 --- .../opds/useOfflineLibraryState.test.tsx | 37 +++++++++++++++++++ src/features/opds/useOfflineLibraryState.ts | 7 +++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/__tests__/features/opds/useOfflineLibraryState.test.tsx b/src/__tests__/features/opds/useOfflineLibraryState.test.tsx index 63e7cba..0b20942 100644 --- a/src/__tests__/features/opds/useOfflineLibraryState.test.tsx +++ b/src/__tests__/features/opds/useOfflineLibraryState.test.tsx @@ -145,6 +145,43 @@ describe("useOfflineLibraryState", () => { }); }); + describe("refreshLibrarySnapshot", () => { + it("surfaces a listing failure through notifyOpdsError and does not throw", async () => { + mockList.mockRejectedValue(new Error("Snapshot read failed")); + + renderOfflineState(); + await waitFor(() => { + expect(mockNotify).toHaveBeenCalledTimes(1); + }); + + expect(mockNotify).toHaveBeenCalledWith( + expect.objectContaining({ message: "Snapshot read failed" }), + { + context: "Offline library", + fallback: "Failed to load the offline library", + }, + ); + }); + + it("updates the snapshot and does not toast on success, preserving prior state on failure", async () => { + const { result } = renderOfflineState(); + await waitFor(() => { + expect(mockList).toHaveBeenCalledTimes(1); + }); + + expect(result.current.libraryInfoByPublicationId["book-1"]?.primary?.revision_id).toBe(11); + expect(mockNotify).not.toHaveBeenCalled(); + + result.current.refreshLibrarySnapshot(); + await waitFor(() => { + expect(mockList).toHaveBeenCalledTimes(2); + }); + + expect(result.current.libraryInfoByPublicationId["book-1"]?.primary?.revision_id).toBe(11); + expect(mockNotify).not.toHaveBeenCalled(); + }); + }); + describe("handleDeleteLocal", () => { it("surfaces the failure through notifyOpdsError and keeps the record visible", async () => { mockDeleteContent.mockRejectedValue(new Error("Permission denied")); diff --git a/src/features/opds/useOfflineLibraryState.ts b/src/features/opds/useOfflineLibraryState.ts index 5764fcb..6ddb62a 100644 --- a/src/features/opds/useOfflineLibraryState.ts +++ b/src/features/opds/useOfflineLibraryState.ts @@ -40,7 +40,12 @@ export function useOfflineLibraryState({ try { const snapshot = await offlineLibraryClient.list(); setLibraryInfoByPublicationId(buildPublicationLibraryInfo(snapshot)); - } catch {} + } catch (error) { + notifyOpdsError(error, { + context: "Offline library", + fallback: "Failed to load the offline library", + }); + } }, []); useEffect(() => {