Recent updates sheet (Feature 21) #176

2 changed files with 56 additions and 3 deletions
Showing only changes of commit d9b63bd08c - Show all commits
+51
View File
@@ -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
View File
@@ -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',
})
}