Files
study-bible-project/server/prefs.test.js
T
nmemmertandClaude Sonnet 5.5 760c1e568b
Build and Push Docker Image / build-and-push (push) Failing after 19s
Sync bookmarks, highlights and display preferences to the account
Bookmarks and text highlights are saved per user and merged across devices
(union on first contact, newer server copy when this device is clean). Theme,
default translation, verse layout, commentary source and reading position sync
too, each with its own timestamp so the newest change to each setting wins.
The Reader never moves under you while it is open. Signing out clears this
device's marks and forgets sync timestamps.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 10:45:23 -04:00

65 lines
3.1 KiB
JavaScript

// @vitest-environment node
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { test, expect } from 'vitest';
import { cleanPrefValue, cleanPrefsPatch, mergePrefs } from './prefs.js';
const NOW = 1_800_000_000_000;
test('only known preferences with valid values are accepted', () => {
expect(cleanPrefValue('theme', 'dark')).toBe('dark');
expect(cleanPrefValue('theme', 'neon')).toBeUndefined();
expect(cleanPrefValue('translation', 'ESV')).toBe('ESV');
expect(cleanPrefValue('translation', 'KJV')).toBeUndefined();
expect(cleanPrefValue('layout', 'flow')).toBe('flow');
expect(cleanPrefValue('commentarySource', 'tyndale')).toBe('tyndale');
expect(cleanPrefValue('fontSize', 2)).toBeUndefined(); // not a synced preference
});
test('position needs a real book and chapter, and the book is normalised', () => {
expect(cleanPrefValue('position', { book: 'jhn', chapter: 3 })).toEqual({ book: 'JHN', chapter: 3 });
expect(cleanPrefValue('position', { book: 'JOHN', chapter: 3 })).toBeUndefined();
expect(cleanPrefValue('position', { book: 'JHN', chapter: 0 })).toBeUndefined();
expect(cleanPrefValue('position', { book: 'JHN', chapter: '3' })).toBeUndefined();
expect(cleanPrefValue('position', null)).toBeUndefined();
});
test('cleanPrefsPatch drops unknown keys, bad values and silly timestamps', () => {
const patch = cleanPrefsPatch({ prefs: {
theme: { v: 'sepia', at: NOW },
layout: { v: 'nope', at: NOW },
evil: { v: 'x', at: NOW },
translation: { v: 'NLT', at: 'yesterday' },
commentarySource: { v: 'tyndale', at: NOW + 5 * 86_400_000 }, // far in the future
position: { v: { book: 'GEN', chapter: 2 }, at: NOW - 10 },
} }, NOW);
expect(patch).toEqual({ theme: { v: 'sepia', at: NOW }, position: { v: { book: 'GEN', chapter: 2 }, at: NOW - 10 } });
expect(cleanPrefsPatch(null)).toEqual({});
expect(cleanPrefsPatch({ prefs: [] })).toEqual({});
});
test('the newest change to each preference wins, independently', () => {
const existing = { theme: { v: 'dark', at: 100 }, layout: { v: 'flow', at: 500 } };
const patch = { theme: { v: 'paper', at: 200 }, layout: { v: 'verses', at: 300 }, translation: { v: 'ESV', at: 50 } };
expect(mergePrefs(existing, patch)).toEqual({
theme: { v: 'paper', at: 200 }, // patch is newer
layout: { v: 'flow', at: 500 }, // stored is newer
translation: { v: 'ESV', at: 50 }, // new key
});
expect(mergePrefs({ theme: { v: 'dark', at: 100 } }, { theme: { v: 'paper', at: 100 } }).theme.v).toBe('dark'); // tie keeps stored
});
test('the database keeps one row per user', async () => {
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), 'prefs-'));
const db = await import('./db.js');
db.initDb();
db.createUser({ id: 'u1', email: 'a@example.test', passwordHash: 'x' });
db.createUser({ id: 'u2', email: 'b@example.test', passwordHash: 'x' });
expect(db.getUserPrefs('u1')).toEqual({});
db.setUserPrefs('u1', { theme: { v: 'dark', at: 1 } });
db.setUserPrefs('u1', { theme: { v: 'sepia', at: 2 } });
expect(db.getUserPrefs('u1')).toEqual({ theme: { v: 'sepia', at: 2 } });
expect(db.getUserPrefs('u2')).toEqual({});
});