feat(pwa): Sync-Status-Store mit localStorage-Persistierung
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m18s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m18s
Spiegelt SW-Messages (sync-start/progress/done/error) in einen Svelte-State. lastSynced wird in localStorage persistiert, damit der User nach einem Reload sieht, wann zuletzt synchronisiert wurde. Wird vom SyncIndicator und der Admin-App-Tab konsumiert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
53
src/lib/client/sync-status.svelte.ts
Normal file
53
src/lib/client/sync-status.svelte.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
// State, den der Service-Worker per postMessage befüllt. Die App
|
||||
// spiegelt den Sync-Fortschritt im SyncIndicator.
|
||||
export type SyncState =
|
||||
| { kind: 'idle' }
|
||||
| { kind: 'syncing'; current: number; total: number }
|
||||
| { kind: 'error'; message: string };
|
||||
|
||||
export type SWMessage =
|
||||
| { type: 'sync-start'; total: number }
|
||||
| { type: 'sync-progress'; current: number; total: number }
|
||||
| { type: 'sync-done'; lastSynced: number }
|
||||
| { type: 'sync-error'; message: string };
|
||||
|
||||
const STORAGE_KEY = 'kochwas.sw.lastSynced';
|
||||
|
||||
function loadLastSynced(): number | null {
|
||||
if (typeof localStorage === 'undefined') return null;
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
const n = Number(raw);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
function saveLastSynced(ts: number): void {
|
||||
if (typeof localStorage === 'undefined') return;
|
||||
localStorage.setItem(STORAGE_KEY, String(ts));
|
||||
}
|
||||
|
||||
class SyncStatusStore {
|
||||
state = $state<SyncState>({ kind: 'idle' });
|
||||
lastSynced = $state<number | null>(loadLastSynced());
|
||||
|
||||
handle(msg: SWMessage): void {
|
||||
switch (msg.type) {
|
||||
case 'sync-start':
|
||||
this.state = { kind: 'syncing', current: 0, total: msg.total };
|
||||
break;
|
||||
case 'sync-progress':
|
||||
this.state = { kind: 'syncing', current: msg.current, total: msg.total };
|
||||
break;
|
||||
case 'sync-done':
|
||||
this.state = { kind: 'idle' };
|
||||
this.lastSynced = msg.lastSynced;
|
||||
saveLastSynced(msg.lastSynced);
|
||||
break;
|
||||
case 'sync-error':
|
||||
this.state = { kind: 'error', message: msg.message };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const syncStatus = new SyncStatusStore();
|
||||
30
tests/unit/sync-status-store.test.ts
Normal file
30
tests/unit/sync-status-store.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
|
||||
describe('sync-status store', () => {
|
||||
beforeEach(async () => {
|
||||
const mod = await import('../../src/lib/client/sync-status.svelte');
|
||||
mod.syncStatus.state = { kind: 'idle' };
|
||||
mod.syncStatus.lastSynced = null;
|
||||
});
|
||||
|
||||
it('processes progress messages', async () => {
|
||||
const { syncStatus } = await import('../../src/lib/client/sync-status.svelte');
|
||||
syncStatus.handle({ type: 'sync-progress', current: 3, total: 10 });
|
||||
expect(syncStatus.state).toEqual({ kind: 'syncing', current: 3, total: 10 });
|
||||
});
|
||||
|
||||
it('transitions to idle on sync-done and records timestamp', async () => {
|
||||
const { syncStatus } = await import('../../src/lib/client/sync-status.svelte');
|
||||
syncStatus.handle({ type: 'sync-start', total: 5 });
|
||||
expect(syncStatus.state.kind).toBe('syncing');
|
||||
syncStatus.handle({ type: 'sync-done', lastSynced: 1700000000000 });
|
||||
expect(syncStatus.state).toEqual({ kind: 'idle' });
|
||||
expect(syncStatus.lastSynced).toBe(1700000000000);
|
||||
});
|
||||
|
||||
it('sets error state on sync-error', async () => {
|
||||
const { syncStatus } = await import('../../src/lib/client/sync-status.svelte');
|
||||
syncStatus.handle({ type: 'sync-error', message: 'Quota exceeded' });
|
||||
expect(syncStatus.state).toEqual({ kind: 'error', message: 'Quota exceeded' });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user