Phase 4.3 (Background-Sync): - webapp/src/sync-queue.ts: localStorage-basierte Queue - enqueueSync, dequeueSync, loadQueue, saveQueue, clearQueue - TypeScript mit korrekten Typen - webapp/src/EditorWithLoad.tsx: erweitert - Auto-Sync-Toggle (localStorage-Persistenz) - Online-Event-Listener: processQueue bei reconnect - Bei Auto-Sync: direkt speichern + sync - Bei Offline: in Queue, wird bei 'online' verarbeitet - Pending-Counter im UI - webapp/public/service-worker.js: Background-Sync - 'sync'-Event-Handler mit tag 'mindomat-sync' - Ruft POST /api/sync, postet Ergebnis an Clients - Browser-Support: Chrome/Edge (Background-Sync API) Tests: - tests/sync-queue.test.ts: 8 neue Tests - Startzustand, enqueue, FIFO, dequeue, clearQueue - Roundtrip, kaputtes JSON - localStorage-Mock Verifiziert: - 79/79 Tests gruen (10 neu seit v2.0.4) - Webapp tsc + build OK - EditorWithLoad funktional mit Auto-Sync-Option Pfad-Korrekturen: - vite.config.ts: ../../src/ -> ../src/ (war falsch, webapp ist nur 1 Ebene tiefer)
89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
// Tests fuer Sync-Queue
|
|
// Phase 4.3
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
|
// Mock localStorage vor dem Import (das Modul nutzt es zur Initialisierung)
|
|
const store = new Map<string, string>();
|
|
(globalThis as any).localStorage = {
|
|
getItem: (key: string) => store.get(key) ?? null,
|
|
setItem: (key: string, value: string) => { store.set(key, value); },
|
|
removeItem: (key: string) => { store.delete(key); },
|
|
clear: () => { store.clear(); },
|
|
key: (i: number) => Array.from(store.keys())[i] ?? null,
|
|
get length() { return store.size; },
|
|
};
|
|
|
|
import {
|
|
enqueueSync,
|
|
dequeueSync,
|
|
loadQueue,
|
|
clearQueue,
|
|
saveQueue,
|
|
} from '../webapp/src/sync-queue';
|
|
|
|
describe('Sync-Queue', () => {
|
|
beforeEach(() => {
|
|
clearQueue();
|
|
});
|
|
|
|
it('Startzustand: leere Queue', () => {
|
|
expect(loadQueue()).toEqual([]);
|
|
});
|
|
|
|
it('enqueue fuegt Element hinzu', () => {
|
|
enqueueSync('note1.md', '# Inhalt 1');
|
|
const queue = loadQueue();
|
|
expect(queue.length).toBe(1);
|
|
expect(queue[0].noteId).toBe('note1.md');
|
|
expect(queue[0].content).toBe('# Inhalt 1');
|
|
expect(typeof queue[0].timestamp).toBe('number');
|
|
});
|
|
|
|
it('enqueue fuegt mehrere Elemente hinzu (FIFO)', () => {
|
|
enqueueSync('a.md', 'A');
|
|
enqueueSync('b.md', 'B');
|
|
enqueueSync('c.md', 'C');
|
|
const queue = loadQueue();
|
|
expect(queue.length).toBe(3);
|
|
expect(queue.map((q) => q.noteId)).toEqual(['a.md', 'b.md', 'c.md']);
|
|
});
|
|
|
|
it('dequeue entfernt Element nach noteId', () => {
|
|
enqueueSync('a.md', 'A');
|
|
enqueueSync('b.md', 'B');
|
|
dequeueSync('a.md');
|
|
const queue = loadQueue();
|
|
expect(queue.length).toBe(1);
|
|
expect(queue[0].noteId).toBe('b.md');
|
|
});
|
|
|
|
it('dequeue unbekannter noteId ist no-op', () => {
|
|
enqueueSync('a.md', 'A');
|
|
dequeueSync('nicht-da.md');
|
|
expect(loadQueue().length).toBe(1);
|
|
});
|
|
|
|
it('clearQueue leert komplett', () => {
|
|
enqueueSync('a.md', 'A');
|
|
enqueueSync('b.md', 'B');
|
|
clearQueue();
|
|
expect(loadQueue()).toEqual([]);
|
|
});
|
|
|
|
it('saveQueue/loadQueue roundtrip mit localStorage', () => {
|
|
const queue = [
|
|
{ noteId: 'a.md', content: 'A', timestamp: 1000 },
|
|
{ noteId: 'b.md', content: 'B', timestamp: 2000 },
|
|
];
|
|
saveQueue(queue);
|
|
const loaded = loadQueue();
|
|
expect(loaded).toEqual(queue);
|
|
});
|
|
|
|
it('kaputtes JSON in localStorage wird als leere Queue behandelt', () => {
|
|
store.set('mindomat-pending-syncs', '{invalid json');
|
|
expect(loadQueue()).toEqual([]);
|
|
});
|
|
});
|