Files
study-bible-project/src/test/App.autosave.test.jsx
T
nmemmertandClaude Sonnet 5.5 f274bc9c8a
Build and Push Docker Image / build-and-push (push) Successful in 7m14s
Harden sync and security; add IQ Bible, templates, password reset, backups
Sync and data safety
- Autosave flushes on leaving a study or the tab; storage-full errors are reported
- Unsynced studies, deletes and ink are queued and retried when back online
- Deletes propagate across devices (with a "Keep it" undo) and no longer resurrect
- Server assigns study versions (no client-clock conflicts); "Combine both"
  merges a conflicted study chunk by chunk
- Reading-plan progress made offline is merged, not overwritten; plans, ink and
  marks are cleared from the device on sign-out

Security
- Block SSRF through the ntfy reminder URL; failure-only rate limiting per IP and
  account; constant-time login; TOTP replay protection; input validation;
  SESSION_SECRET required in production; admin user delete fixed
- Forgot password: one-time code sent to the account's ntfy topic

Features
- IQ Bible proxy (route allow-list, text-only caching per its terms): more
  commentaries, lexicons, study aids, daily verse and devotional, plan calendar
  export, narrations, podcast background
- Study templates, reading-plan catch-up, account export, daily database snapshots

Tests and tooling
- ESLint config; App tests split into src/test; tests for sync queues, library
  hook, merge, templates, IQ Bible UI and reset form

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 08:13:36 -04:00

32 lines
1.5 KiB
React

import { screen } from '@testing-library/react';
import { describe, test, expect } from 'vitest';
import { installAppLifecycle, loadChapter, addChunk } from './appHarness.jsx';
import { loadProjectIndex } from '../utils/bibleUtils.jsx';
import { loadProjectById } from '../utils/projectStorage.js';
import { unsyncedStudies } from '../utils/syncQueue.js';
installAppLifecycle();
describe('Autosave', () => {
test('leaving a study straight after an edit still saves it (no waiting for the 1-second timer)', async () => {
const user = await loadChapter();
addChunk('Paul, a servant', 'In hope of eternal');
await screen.findByText('1 created');
await user.click(screen.getByRole('button', { name: /bible study home|back to home/i }));
await screen.findByText('My Studies');
const [entry] = loadProjectIndex();
expect(entry).toBeTruthy();
expect(loadProjectById(entry.id).chapters[0].chunks).toHaveLength(1);
});
test('a saved study is queued for the server until the server accepts it', async () => {
const user = await loadChapter();
addChunk('Paul, a servant', 'In hope of eternal');
await screen.findByText('1 created');
await user.click(screen.getByRole('button', { name: /bible study home|back to home/i }));
await screen.findByText('My Studies');
// the mocked server is unreachable in these tests, so the study stays on the "not yet synced" list
expect(unsyncedStudies.list()).toContain(loadProjectIndex()[0].id);
});
});