import { beforeEach, expect, test } from 'vitest'; import { backupFileName, buildBackup, mergeReadingPlans, restoreProjects, validateBackup } from './backup.js'; import { loadProjectById, saveProjectToStorage } from './projectStorage.js'; import { switchStorageUser } from './bibleUtils.jsx'; const project = (id, lastEdited, title = 'Romans') => ({ id, title, translation: 'BSB', lastEdited, selectedChunkId: null, chapters: [{ book: 'Romans', bookAbbrev: 'ROM', chapter: '8', verses: [{ number: 1, text: 'x' }], chunks: [] }], }); beforeEach(() => { localStorage.clear(); switchStorageUser(null); }); test('a backup holds the studies, marks and plans, and round-trips through validation', () => { saveProjectToStorage(project('p1', 100)); const backup = buildBackup({ reader: { bookmarks: { 'JHN-3-16': '#fef08a' }, highlights: [{ id: 'h1' }], colorLabels: { '#fef08a': 'Key' }, ink: { JHN_3: [] } }, readingPlans: { progress: [{ id: 'r1' }], customPlans: [], activity: ['2026-09-01'] }, now: new Date('2026-10-01T12:00:00Z'), }); expect(backup.projects.map((p) => p.id)).toEqual(['p1']); const checked = validateBackup(JSON.parse(JSON.stringify(backup))); expect(checked.ok).toBe(true); expect(checked.summary).toEqual({ projects: 1, bookmarks: 1, highlights: 1, inkPages: 1, plans: 1 }); expect(backupFileName(new Date('2026-10-01T12:00:00Z'))).toBe('bible-study-backup-2026-10-01.json'); }); test('rejects files that are not backups, or are from a newer version', () => { expect(validateBackup(null).ok).toBe(false); expect(validateBackup({ app: 'something-else', version: 1 }).ok).toBe(false); expect(validateBackup({ app: 'bible-study-project', version: 99 }).ok).toBe(false); }); test('ignores malformed entries instead of failing', () => { const checked = validateBackup({ app: 'bible-study-project', version: 1, projects: [{ id: 'ok', title: 'T', chapters: [] }, { nope: true }, null], reader: { highlights: [{ id: 'h' }, 5] } }); expect(checked.ok).toBe(true); expect(checked.summary).toMatchObject({ projects: 1, highlights: 1 }); }); test('restoring adds missing studies and only replaces older copies', () => { saveProjectToStorage(project('newer', 500, 'Local newer')); saveProjectToStorage(project('older', 100, 'Local older')); const written = restoreProjects([project('newer', 200, 'Backup'), project('older', 300, 'Backup newer'), project('missing', 50, 'Brought back')]); expect(written.map((p) => p.id).sort()).toEqual(['missing', 'older']); expect(loadProjectById('newer').title).toBe('Local newer'); expect(loadProjectById('older').title).toBe('Backup newer'); expect(loadProjectById('missing').title).toBe('Brought back'); }); test('merging plans keeps every run once, preferring the later activity', () => { const merged = mergeReadingPlans( { progress: [{ id: 'a', lastActivityAt: 10, completedDays: [1] }], customPlans: [{ id: 'c1' }], activity: ['2026-09-02'] }, { progress: [{ id: 'a', lastActivityAt: 20, completedDays: [1, 2] }, { id: 'b', lastActivityAt: 5 }], customPlans: [{ id: 'c1' }, { id: 'c2' }], activity: ['2026-09-01', '2026-09-02'] }, ); expect(merged.progress.find((p) => p.id === 'a').completedDays).toEqual([1, 2]); expect(merged.progress).toHaveLength(2); expect(merged.customPlans.map((p) => p.id)).toEqual(['c1', 'c2']); expect(merged.activity).toEqual(['2026-09-01', '2026-09-02']); });