Recent updates sheet (Feature 21) #176
@@ -257,6 +257,57 @@ describe('Recent updates sheet', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('closing the sheet while its first read is in flight cancels that read, and a reopen reads afresh', async () => {
|
||||
const stored = [...UPDATES.updates]
|
||||
let firstAborted = false
|
||||
let answerFirst: (() => void) | undefined
|
||||
let reads = 0
|
||||
const fetchMock = stubServer({
|
||||
[`GET ${UPDATES_PATH}`]: (init) => {
|
||||
const body = { updates: [...stored] }
|
||||
reads++
|
||||
if (reads > 1) return jsonResponse(200, body)
|
||||
// The first reading is held open, and fails as fetch does if it is
|
||||
// aborted.
|
||||
return new Promise<Response>((resolve, reject) => {
|
||||
answerFirst = () => resolve(jsonResponse(200, body))
|
||||
init?.signal?.addEventListener('abort', () => {
|
||||
firstAborted = true
|
||||
reject(init.signal!.reason as Error)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
await renderHome()
|
||||
await openSheet()
|
||||
await waitFor(() => expect(updatesReads(fetchMock)).toBe(1))
|
||||
|
||||
// Closed before the first read answers, a new event stored, and opened
|
||||
// again at once.
|
||||
fireEvent.keyDown(screen.getByRole('dialog'), { key: 'Escape' })
|
||||
const reminder = {
|
||||
type: 'day_of_reminder',
|
||||
title: 'Tacos tonight',
|
||||
body: 'Dinner at 6:00.',
|
||||
url: '/plan',
|
||||
created_at: new Date().toISOString(),
|
||||
}
|
||||
stored.unshift(reminder)
|
||||
fireEvent.click(screen.getByRole('button', { name: /^Recent updates/ }))
|
||||
const sheet = screen.getByRole('dialog', { name: 'Recent updates' })
|
||||
|
||||
// The first read was cancelled, and the reopen sent its own.
|
||||
await waitFor(() => expect(updatesReads(fetchMock)).toBe(2))
|
||||
expect(firstAborted).toBe(true)
|
||||
// Answering the cancelled read changes nothing.
|
||||
answerFirst!()
|
||||
|
||||
const listed = await rows(sheet)
|
||||
expect(listed).toHaveLength(4)
|
||||
expect(listed[0]).toHaveTextContent('Tacos tonight')
|
||||
await waitFor(() => expect(posts(fetchMock)).toEqual([JSON.stringify({ through: reminder.created_at })]))
|
||||
})
|
||||
|
||||
it.each([
|
||||
[0, '/plan'],
|
||||
[1, '/discover'],
|
||||
|
||||
+5
-3
@@ -146,13 +146,15 @@ export function parseUpdates(where: string, body: unknown, origin: string): Upda
|
||||
// useUpdates is the Recent updates (GET /api/v1/updates): the last ten
|
||||
// notification events, newest first. Each opening of the sheet reads it
|
||||
// afresh (refetchOnMount 'always'), whatever the query client still holds;
|
||||
// the sheet shows, and marks seen, only the list that opening read.
|
||||
// the sheet shows, and marks seen, only the list that opening read. Closing
|
||||
// the sheet aborts a read still in flight, so a reopen sends its own rather
|
||||
// than adopt one sent before it.
|
||||
export function useUpdates() {
|
||||
const { apiFetch } = useApi()
|
||||
return useQuery({
|
||||
queryKey: UPDATES_KEY,
|
||||
queryFn: async () =>
|
||||
parseUpdates(`GET ${UPDATES_PATH}`, await apiFetch<unknown>(UPDATES_PATH), window.location.origin),
|
||||
queryFn: async ({ signal }) =>
|
||||
parseUpdates(`GET ${UPDATES_PATH}`, await apiFetch<unknown>(UPDATES_PATH, { signal }), window.location.origin),
|
||||
refetchOnMount: 'always',
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user