feat(03-06): implement SyncStateToast with polled sync-status feedback (D-05/D-06/D-08/D-09)

- SyncStateToast: pending/done/failed/dead states per UI-SPEC
- refetchInterval 3000ms while pending; disabled on terminal status
- done + conflict (412) invalidate ['events'] cache (D-06/D-08)
- done auto-dismisses after 2s; failed/dead persist with dismiss button
- role=status (pending/done) and role=alert (failed/dead) for a11y
- Mounted in CalendarShell (both phone + tablet/desktop layouts)
- EventForm.onSuccess: setLastSyncedUid(uid) instead of invalidateQueries
This commit is contained in:
Lucas Berger
2026-06-05 18:43:36 -04:00
parent 6874e1a074
commit aa7c4c37d4
5 changed files with 286 additions and 50 deletions
+27 -12
View File
@@ -28,11 +28,13 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const {
mockSetEventForm,
mockSetLastSyncedUid,
mockCreateEvent,
mockUpdateEvent,
mockFetchWritableCalendars,
} = vi.hoisted(() => ({
mockSetEventForm: vi.fn(),
mockSetLastSyncedUid: vi.fn(),
mockCreateEvent: vi.fn().mockResolvedValue({ uid: 'new-uid-123' }),
mockUpdateEvent: vi.fn().mockResolvedValue({ uid: 'edit-uid-456' }),
mockFetchWritableCalendars: vi.fn(),
@@ -43,12 +45,18 @@ let mockEventFormMode: 'create' | 'edit' = 'create'
let mockEventFormUid: string | null = null
vi.mock('../store/calendarStore.js', () => ({
useCalendarStore: vi.fn(() => ({
eventFormOpen: mockEventFormOpen,
eventFormMode: mockEventFormMode,
eventFormUid: mockEventFormUid,
setEventForm: mockSetEventForm,
})),
useCalendarStore: vi.fn((selector?: (s: Record<string, unknown>) => unknown) => {
const state = {
eventFormOpen: mockEventFormOpen,
eventFormMode: mockEventFormMode,
eventFormUid: mockEventFormUid,
setEventForm: mockSetEventForm,
setLastSyncedUid: mockSetLastSyncedUid,
}
// Support both selector form and plain call form
if (typeof selector === 'function') return selector(state)
return state
}),
}))
vi.mock('../api/client.js', () => ({
@@ -106,12 +114,19 @@ function renderForm(options: {
mockEventFormOpen = true
mockEventFormMode = mode
mockEventFormUid = uid
;(useCalendarStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
eventFormOpen: true,
eventFormMode: mode,
eventFormUid: uid,
setEventForm: mockSetEventForm,
}))
;(useCalendarStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(
(selector?: (s: Record<string, unknown>) => unknown) => {
const state = {
eventFormOpen: true,
eventFormMode: mode,
eventFormUid: uid,
setEventForm: mockSetEventForm,
setLastSyncedUid: mockSetLastSyncedUid,
}
if (typeof selector === 'function') return selector(state)
return state
},
)
mockFetchWritableCalendars.mockResolvedValue(calendars)