diff --git a/__tests__/lib/highlight-sync-manager.test.ts b/__tests__/lib/highlight-sync-manager.test.ts index a5093eb..2c3a966 100644 --- a/__tests__/lib/highlight-sync-manager.test.ts +++ b/__tests__/lib/highlight-sync-manager.test.ts @@ -76,4 +76,31 @@ describe('HighlightSyncManager', () => { const syncing = await manager.getSyncingItems() expect(syncing.length).toBe(1) }) + + it('should perform sync and mark items as synced', async () => { + const highlight: BibleHighlight = { + id: 'h-1', + verseId: 'v-1', + color: 'yellow', + createdAt: Date.now(), + updatedAt: Date.now(), + syncStatus: 'pending' + } + + await manager.queueHighlight(highlight) + await manager.init() + + // Mock fetch + global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ synced: 1, errors: [] }) + }) + ) as jest.Mock + + const result = await manager.performSync() + + expect(result.synced).toBe(1) + expect(result.errors).toBe(0) + }) }) diff --git a/lib/highlight-sync-manager.ts b/lib/highlight-sync-manager.ts index 7f628e0..1988e2c 100644 --- a/lib/highlight-sync-manager.ts +++ b/lib/highlight-sync-manager.ts @@ -108,11 +108,69 @@ export class HighlightSyncManager { } } - startAutoSync(intervalMs: number = 30000, onSyncNeeded?: () => void) { - this.syncInterval = setInterval(async () => { + async performSync(): Promise<{ synced: number; errors: number }> { + if (!this.db) await this.init() + + try { const pending = await this.getPendingSyncItems() - if (pending.length > 0 && onSyncNeeded) { - onSyncNeeded() + if (pending.length === 0) return { synced: 0, errors: 0 } + + // Mark as syncing + await this.markSyncing(pending.map(h => h.id)) + + // POST to backend + const response = await fetch('/api/highlights/bulk', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ highlights: pending }) + }) + + if (!response.ok) { + // Mark all as error + const errorIds = pending.map(h => h.id) + await this.markError(errorIds, `HTTP ${response.status}`) + return { synced: 0, errors: pending.length } + } + + const result = await response.json() + + // Mark successfully synced items + if (result.synced > 0) { + const syncedIds = pending + .filter(h => !result.errors.some((e: any) => e.verseId === h.verseId)) + .map(h => h.id) + await this.markSynced(syncedIds) + } + + // Mark errored items + if (result.errors && result.errors.length > 0) { + for (const error of result.errors) { + const h = pending.find(item => item.verseId === error.verseId) + if (h) { + await this.markError([h.id], error.error) + } + } + } + + return { synced: result.synced, errors: result.errors?.length || 0 } + } catch (error) { + console.error('Sync failed:', error) + const pending = await this.getPendingSyncItems() + if (pending.length > 0) { + await this.markError( + pending.map(h => h.id), + 'Network error' + ) + } + return { synced: 0, errors: pending.length } + } + } + + startAutoSync(intervalMs: number = 30000, onSyncNeeded?: (result: { synced: number; errors: number }) => void) { + this.syncInterval = setInterval(async () => { + const result = await this.performSync() + if (result.synced > 0 || result.errors > 0) { + onSyncNeeded?.(result) } }, intervalMs) }