fix(notifications): type pages and guard missing rollback state

Specify the infinite query response and page parameter types. Preserve
the original mutation error when optimistic preparation fails before
rollback state is available.

Add a regression for failed optimistic preparation. All 19 API tests,
42 component tests, formatting and source review pass. Full types, lint,
coverage and remaining WIP gates still need validation.
This commit is contained in:
FredApps 2026-10-10 16:48:27 +02:00
parent 7e8f58f854
commit 2ebcbfac3c
2 changed files with 48 additions and 2 deletions

View File

@ -1,5 +1,5 @@
import type { EntryKey, UseInfiniteQueryData } from "@pinia/colada"
import { afterEach, beforeEach, describe, it } from "vitest"
import { afterEach, beforeEach, describe, it, vi } from "vitest"
import {
clearQueryCache,
disposeMockEndpoints,
@ -305,6 +305,48 @@ describe("useNotificationAPI", { concurrent: false }, () => {
})
describe("markNotificationsRead", () => {
it("preserves the error when preparing the optimistic update fails", async ({
expect,
}) => {
const listCalls = mockEndpoint("GET", "/api/notifications", () =>
makePage([]),
)
const countCalls = mockEndpoint(
"GET",
"/api/notifications/count",
() => ({ count: 0 }),
)
const putCalls = mockEndpoint(
"PUT",
"/api/notifications/read-status",
() => ({}),
)
const page = makePage([makeNotification("n1", false)])
seedPages(LIST_KEY_A, page)
const api = makeNotificationAPI()
const queryCache = runInApp(() => useQueryCache())
const error = new Error("Cache unavailable")
const getQueryData = vi
.spyOn(queryCache, "getQueryData")
.mockImplementationOnce(() => {
throw error
})
try {
await expect(
api.markNotificationsRead.mutateAsync({ ids: ["n1"] }),
).rejects.toBe(error)
expect(getQueryData).toHaveBeenCalledExactlyOnceWith(LIST_KEY_A)
expect(putCalls).toHaveLength(0)
expect(listCalls).toHaveLength(0)
expect(countCalls).toHaveLength(0)
} finally {
getQueryData.mockRestore()
}
expect(getPages(LIST_KEY_A)).toEqual(makePages(page))
})
it.for([
{ name: "one notification", ids: ["n1"], remaining: ["n2"] },
{ name: "all notifications", ids: [], remaining: [] },

View File

@ -18,7 +18,7 @@ export default function () {
function useFetchManyNotifications(
paramsRef: MaybeRefOrGetter<NotificationsParams>,
) {
return useInfiniteQuery(() => {
return useInfiniteQuery<NotificationsResponse, Error, number>(() => {
const { limit, read } = toValue(paramsRef)
return {
@ -136,6 +136,10 @@ export default function () {
},
onSuccess: invalidateNotifications,
onError(_err, _data, { oldNotifs, newNotifs }) {
if (!newNotifs) {
return
}
const entries = queryCache.getEntries({
key: NOTIFICATION_QUERY_KEYS.listRoot,
// switching filters can populate another cache while the