diff --git a/apps/pwa/src/api/client.test.ts b/apps/pwa/src/api/client.test.ts index 7f6a2f5..7bee75c 100644 --- a/apps/pwa/src/api/client.test.ts +++ b/apps/pwa/src/api/client.test.ts @@ -200,6 +200,115 @@ describe('fetchWritableCalendars', () => { }) }) +// ── deleteEvent tests ───────────────────────────────────────────────────────── + +describe('deleteEvent', () => { + beforeEach(() => { + vi.stubGlobal('fetch', vi.fn()) + }) + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('DELETEs /api/events/:uid with credentials:include', async () => { + const mockFetch = vi.mocked(fetch) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => ({}), + } as Response) + + const { deleteEvent } = await import('./client.js') + await deleteEvent('uid-to-delete') + + expect(mockFetch).toHaveBeenCalledWith( + '/api/events/uid-to-delete', + expect.objectContaining({ + method: 'DELETE', + credentials: 'include', + }), + ) + }) + + it('resolves void on success (204/202)', async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({}), + } as Response) + + const { deleteEvent } = await import('./client.js') + const result = await deleteEvent('uid-abc') + expect(result).toBeUndefined() + }) + + it('throws on non-ok response', async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: false, + status: 404, + } as Response) + + const { deleteEvent } = await import('./client.js') + await expect(deleteEvent('missing-uid')).rejects.toThrow() + }) +}) + +// ── fetchSyncStatus tests ───────────────────────────────────────────────────── + +describe('fetchSyncStatus', () => { + beforeEach(() => { + vi.stubGlobal('fetch', vi.fn()) + }) + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('GETs /api/events/sync-status?uid= with credentials:include', async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ uid: 'my-uid', status: 'pending' }), + } as Response) + + const { fetchSyncStatus } = await import('./client.js') + await fetchSyncStatus('my-uid') + + expect(vi.mocked(fetch)).toHaveBeenCalledWith( + '/api/events/sync-status?uid=my-uid', + expect.objectContaining({ credentials: 'include' }), + ) + }) + + it('returns SyncStatus object with uid and status', async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ uid: 'test-uid', status: 'done' }), + } as Response) + + const { fetchSyncStatus } = await import('./client.js') + const result = await fetchSyncStatus('test-uid') + expect(result).toEqual({ uid: 'test-uid', status: 'done' }) + }) + + it('returns SyncStatus with error field for failed status', async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ uid: 'fail-uid', status: 'failed', error: '412 Conflict' }), + } as Response) + + const { fetchSyncStatus } = await import('./client.js') + const result = await fetchSyncStatus('fail-uid') + expect(result).toEqual({ uid: 'fail-uid', status: 'failed', error: '412 Conflict' }) + }) + + it('throws on non-ok response', async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: false, + status: 500, + } as Response) + + const { fetchSyncStatus } = await import('./client.js') + await expect(fetchSyncStatus('any-uid')).rejects.toThrow() + }) +}) + // ── Zustand store new keys tests ────────────────────────────────────────────── describe('calendarStore — eventForm keys', () => { @@ -249,3 +358,54 @@ describe('calendarStore — eventForm keys', () => { expect(state.eventFormUid).toBeNull() }) }) + +describe('calendarStore — delete/sync keys', () => { + it('deleteDialogOpen defaults to false', async () => { + const { useCalendarStore } = await import('../store/calendarStore.js') + const state = useCalendarStore.getState() + expect(state.deleteDialogOpen).toBe(false) + }) + + it('deleteDialogUid defaults to null', async () => { + const { useCalendarStore } = await import('../store/calendarStore.js') + const state = useCalendarStore.getState() + expect(state.deleteDialogUid).toBeNull() + }) + + it('lastSyncedUid defaults to null', async () => { + const { useCalendarStore } = await import('../store/calendarStore.js') + const state = useCalendarStore.getState() + expect(state.lastSyncedUid).toBeNull() + }) + + it('setDeleteDialog(true, uid) opens dialog with uid', async () => { + const { useCalendarStore } = await import('../store/calendarStore.js') + useCalendarStore.getState().setDeleteDialog(true, 'delete-uid-999') + const state = useCalendarStore.getState() + expect(state.deleteDialogOpen).toBe(true) + expect(state.deleteDialogUid).toBe('delete-uid-999') + }) + + it('setDeleteDialog(false) closes dialog', async () => { + const { useCalendarStore } = await import('../store/calendarStore.js') + useCalendarStore.getState().setDeleteDialog(true, 'some-uid') + useCalendarStore.getState().setDeleteDialog(false) + const state = useCalendarStore.getState() + expect(state.deleteDialogOpen).toBe(false) + }) + + it('setLastSyncedUid sets the UID', async () => { + const { useCalendarStore } = await import('../store/calendarStore.js') + useCalendarStore.getState().setLastSyncedUid('synced-uid-abc') + const state = useCalendarStore.getState() + expect(state.lastSyncedUid).toBe('synced-uid-abc') + }) + + it('setLastSyncedUid(null) clears the UID', async () => { + const { useCalendarStore } = await import('../store/calendarStore.js') + useCalendarStore.getState().setLastSyncedUid('some-uid') + useCalendarStore.getState().setLastSyncedUid(null) + const state = useCalendarStore.getState() + expect(state.lastSyncedUid).toBeNull() + }) +})