trails/apps/planner/app/lib/crash-recovery.test.ts
Ullrich Schäfer 0a8dd0b766
Add transactional emails (SMTP) and planner features (no-go areas, notes, crash recovery)
Transactional emails:
- Add nodemailer SMTP email module with dev-mode console logging
- Magic link template and welcome template with HTML + plain text
- Wire sendMagicLink into login flow, sendWelcome into registration
- Update privacy page and deploy docs for SMTP configuration

Planner features:
- No-go areas: draw polygons on map (leaflet-geoman), synced via Yjs,
  passed to BRouter as nogos parameter, route recomputes on change
- Session notes: collaborative Y.Text textarea in sidebar tab
- Crash recovery: periodic localStorage save of Yjs state, restore on reconnect
- Rate limit session creation (10/IP/hour) in /new route

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:00:42 +01:00

56 lines
1.8 KiB
TypeScript

import { describe, it, expect } from "vitest";
import * as Y from "yjs";
/**
* Tests the crash recovery encode/decode logic used in use-yjs.ts.
* The actual hook wiring is tested via E2E; these verify the data round-trip.
*/
describe("crash recovery", () => {
it("round-trips Yjs state through base64 localStorage format", () => {
const doc = new Y.Doc();
const waypoints = doc.getArray("waypoints");
const wp = new Y.Map();
wp.set("lat", 52.52);
wp.set("lon", 13.405);
waypoints.push([wp]);
// Encode (same as use-yjs.ts save)
const state = Y.encodeStateAsUpdate(doc);
const b64 = btoa(String.fromCharCode(...state));
// Decode (same as use-yjs.ts restore)
const restored = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
const doc2 = new Y.Doc();
Y.applyUpdate(doc2, restored);
const restoredWaypoints = doc2.getArray("waypoints");
expect(restoredWaypoints.length).toBe(1);
const restoredWp = restoredWaypoints.get(0) as Y.Map<unknown>;
expect(restoredWp.get("lat")).toBe(52.52);
expect(restoredWp.get("lon")).toBe(13.405);
});
it("merges local and remote state without conflict", () => {
// Simulate: local has waypoint A, server has waypoint B
const local = new Y.Doc();
const localWps = local.getArray("waypoints");
const wpA = new Y.Map();
wpA.set("lat", 52.0);
wpA.set("lon", 13.0);
localWps.push([wpA]);
const remote = new Y.Doc();
const remoteWps = remote.getArray("waypoints");
const wpB = new Y.Map();
wpB.set("lat", 48.0);
wpB.set("lon", 11.0);
remoteWps.push([wpB]);
// Merge remote into local (simulates reconnect)
const remoteState = Y.encodeStateAsUpdate(remote);
Y.applyUpdate(local, remoteState);
// Both waypoints should exist
expect(localWps.length).toBe(2);
});
});