Build and Push Docker Image / build-and-push (push) Successful in 5m13s
- Earlier versions of a study: the server keeps compressed copies (about one per ten minutes of editing, the latest 30); Export shows what differs and puts one back (the replaced copy is kept) - Notes on highlights and bookmarks: synced, merged without loss, in exports and backups - Reader verse panel lists your studies and chunks that cover the verse, and opens them - Prayer journal: requests, who for, linked verses, days prayed, answers; synced, in backups and the account export; daily prayer reminder through ntfy - TODO.md lists the ideas not built yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
40 lines
2.8 KiB
JavaScript
40 lines
2.8 KiB
JavaScript
// @vitest-environment node
|
|
import { test, expect } from 'vitest';
|
|
import { cleanPrayer, cleanPrayers, mergePrayers, openPrayers } from './prayers.js';
|
|
|
|
const NOW = 1_800_000_000_000;
|
|
const p = (id, extra = {}) => ({ id, text: `Pray ${id}`, forWhom: '', verses: [], prayedOn: [], createdAt: NOW - 5000, updatedAt: NOW - 1000, answeredAt: null, answerNote: '', ...extra });
|
|
|
|
test('only well-formed prayers are kept, rebuilt field by field', () => {
|
|
const out = cleanPrayer({ id: 'a-1', text: ' For my family ', forWhom: 'Home', evil: '<script>', verses: [{ book: 'PHP', chapter: 4, verse: 6, label: 'Philippians 4:6' }, { book: 'xx', chapter: 1 }, { book: 'JHN', chapter: 99999 }], prayedOn: ['2026-10-01', 'nope', '2026-10-01'], updatedAt: NOW - 1 }, NOW);
|
|
expect(out).toMatchObject({ id: 'a-1', text: 'For my family', forWhom: 'Home', prayedOn: ['2026-10-01'], answeredAt: null });
|
|
expect(out.verses).toEqual([{ book: 'PHP', chapter: 4, verse: 6, label: 'Philippians 4:6' }]);
|
|
expect('evil' in out).toBe(false);
|
|
expect(cleanPrayer({ id: 'bad id!', text: 'x' }, NOW)).toBeNull();
|
|
expect(cleanPrayer({ id: 'a', text: ' ' }, NOW)).toBeNull();
|
|
expect(cleanPrayer(null)).toBeNull();
|
|
expect(cleanPrayer({ id: 'g', deleted: true, updatedAt: NOW }, NOW)).toEqual({ id: 'g', deleted: true, updatedAt: NOW });
|
|
expect(cleanPrayers([p('a'), p('a'), 5, p('b')], NOW).map((x) => x.id)).toEqual(['a', 'b']);
|
|
});
|
|
|
|
test('the newer version of a prayer wins and the days prayed are combined', () => {
|
|
const mine = [p('a', { text: 'old wording', prayedOn: ['2026-10-01'], updatedAt: NOW - 5000 })];
|
|
const theirs = [p('a', { text: 'new wording', prayedOn: ['2026-10-02'], updatedAt: NOW - 1000 }), p('b')];
|
|
const merged = mergePrayers(mine, theirs, NOW);
|
|
expect(merged.find((x) => x.id === 'a')).toMatchObject({ text: 'new wording', prayedOn: ['2026-10-01', '2026-10-02'] });
|
|
expect(merged.map((x) => x.id).sort()).toEqual(['a', 'b']);
|
|
expect(mergePrayers(merged, merged, NOW)).toEqual(merged);
|
|
});
|
|
|
|
test('a deleted prayer is not brought back by an older copy, and old markers are forgotten', () => {
|
|
const gone = [{ id: 'a', deleted: true, updatedAt: NOW - 100 }];
|
|
const stale = [p('a', { updatedAt: NOW - 9000 })];
|
|
expect(mergePrayers(gone, stale, NOW)).toEqual([{ id: 'a', deleted: true, updatedAt: NOW - 100 }]);
|
|
expect(mergePrayers([{ id: 'z', deleted: true, updatedAt: NOW - 100 * 86_400_000 }], [], NOW)).toEqual([]);
|
|
expect(mergePrayers([p('a', { updatedAt: NOW })], [{ id: 'a', deleted: true, updatedAt: NOW - 5 }], NOW)[0].deleted).toBeUndefined(); // written again after the delete
|
|
});
|
|
|
|
test('open prayers are the ones not answered or deleted', () => {
|
|
expect(openPrayers([p('a'), p('b', { answeredAt: NOW }), { id: 'c', deleted: true, updatedAt: 1 }]).map((x) => x.id)).toEqual(['a']);
|
|
});
|