Build and Push Docker Image / build-and-push (push) Successful in 7m14s
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>
32 lines
1.5 KiB
React
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);
|
|
});
|
|
});
|