131 lines
3.9 KiB
TypeScript
131 lines
3.9 KiB
TypeScript
/**
|
|
* Task CAD-12a - Autosave-Service.
|
|
*
|
|
* Der Yjs-CRDT speichert bereits automatisch ueber WebSocket.
|
|
* Dieser Service macht das SICHTBAR und ergaenzt:
|
|
* - Save-Status-Text (Automatisch gespeichert / Offline / Speichert...)
|
|
* - Periodischer Full-Save in SQLite (Redundanz)
|
|
* - Reconnect-Handling (CRDT synced automatisch nach)
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import {
|
|
createAutosaveController,
|
|
type AutosaveStatus,
|
|
AUTOSAVE_INTERVAL_MS,
|
|
} from '../src/services/autosaveService';
|
|
|
|
describe('CAD-12a: autosaveService', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('AUTOSAVE_INTERVAL_MS ist 30 Sekunden', () => {
|
|
expect(AUTOSAVE_INTERVAL_MS).toBe(30000);
|
|
});
|
|
|
|
it('Controller: Status ist "idle" wenn kein CRDT verbunden', () => {
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => false,
|
|
hasUnsavedChanges: () => false,
|
|
performFullSave: vi.fn(),
|
|
onStatusChange: vi.fn(),
|
|
});
|
|
const status: AutosaveStatus = ctrl.getStatus();
|
|
expect(status.state).toBe('offline');
|
|
ctrl.destroy();
|
|
});
|
|
|
|
it('Controller: Status ist "saved" wenn CRDT verbunden und keine ungespeicherten Aenderungen', () => {
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => true,
|
|
hasUnsavedChanges: () => false,
|
|
performFullSave: vi.fn(),
|
|
onStatusChange: vi.fn(),
|
|
});
|
|
expect(ctrl.getStatus().state).toBe('saved');
|
|
ctrl.destroy();
|
|
});
|
|
|
|
it('Controller: Status wechselt zu "saving" wenn Aenderungen vorhanden und Timer feuert', () => {
|
|
const fullSave = vi.fn().mockResolvedValue(undefined);
|
|
const onStatus = vi.fn();
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => true,
|
|
hasUnsavedChanges: () => true,
|
|
performFullSave: fullSave,
|
|
onStatusChange: onStatus,
|
|
});
|
|
vi.advanceTimersByTime(31000);
|
|
expect(fullSave).toHaveBeenCalledTimes(1);
|
|
ctrl.destroy();
|
|
});
|
|
|
|
it('Controller: fuehrt KEINEN Full-Save aus wenn keine Aenderungen', () => {
|
|
const fullSave = vi.fn();
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => true,
|
|
hasUnsavedChanges: () => false,
|
|
performFullSave: fullSave,
|
|
onStatusChange: vi.fn(),
|
|
});
|
|
vi.advanceTimersByTime(31000);
|
|
expect(fullSave).not.toHaveBeenCalled();
|
|
ctrl.destroy();
|
|
});
|
|
|
|
it('Controller: fuehrt KEINEN Full-Save aus wenn CRDT offline', () => {
|
|
const fullSave = vi.fn();
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => false,
|
|
hasUnsavedChanges: () => true,
|
|
performFullSave: fullSave,
|
|
onStatusChange: vi.fn(),
|
|
});
|
|
vi.advanceTimersByTime(31000);
|
|
expect(fullSave).not.toHaveBeenCalled();
|
|
ctrl.destroy();
|
|
});
|
|
|
|
it('Controller: Status-Meldung an onStatusChange bei Aenderung', () => {
|
|
const onStatus = vi.fn();
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => true,
|
|
hasUnsavedChanges: () => false,
|
|
performFullSave: vi.fn(),
|
|
onStatusChange: onStatus,
|
|
});
|
|
expect(onStatus).toHaveBeenCalledWith({ state: 'saved', lastSaved: null });
|
|
ctrl.destroy();
|
|
});
|
|
|
|
it('Controller: markChanges triggert saving beim naechsten Timer-Tick', () => {
|
|
const fullSave = vi.fn().mockResolvedValue(undefined);
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => true,
|
|
hasUnsavedChanges: () => false,
|
|
performFullSave: fullSave,
|
|
onStatusChange: vi.fn(),
|
|
});
|
|
ctrl.markChanges();
|
|
vi.advanceTimersByTime(31000);
|
|
expect(fullSave).toHaveBeenCalled();
|
|
ctrl.destroy();
|
|
});
|
|
|
|
it('Controller: destroy stoppt den Timer', () => {
|
|
const fullSave = vi.fn();
|
|
const ctrl = createAutosaveController({
|
|
isConnected: () => true,
|
|
hasUnsavedChanges: () => true,
|
|
performFullSave: fullSave,
|
|
onStatusChange: vi.fn(),
|
|
});
|
|
ctrl.destroy();
|
|
vi.advanceTimersByTime(60000);
|
|
expect(fullSave).not.toHaveBeenCalled();
|
|
});
|
|
});
|