// @vitest-environment node import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { test, expect } from 'vitest'; import { cleanMarks } from './readerMarks.js'; const hl = (over = {}) => ({ id: 'h1', book: 'JHN', chapter: 3, verse: 16, startOffset: 4, endOffset: 20, color: '#fef08a', ...over }); test('a non-object body is rejected', () => { expect(cleanMarks(null)).toBeNull(); expect(cleanMarks([])).toBeNull(); expect(cleanMarks('x')).toBeNull(); }); test('good bookmarks and highlights pass through unchanged', () => { const body = { bookmarks: { 'JHN-3-16': '#fef08a', '1SA-3-5': '#bbf7d0' }, highlights: [hl()] }; expect(cleanMarks(body)).toEqual(body); }); test('malformed bookmarks are dropped, good ones kept', () => { const out = cleanMarks({ bookmarks: { 'JHN-3-16': '#fef08a', 'bad key': '#fef08a', 'GEN-1-1': 'red', 'GEN-1-2': 5, '../x-1-1': '#fff' } }); expect(out.bookmarks).toEqual({ 'JHN-3-16': '#fef08a' }); }); test('highlights are validated field by field and de-duplicated by id', () => { const out = cleanMarks({ highlights: [ hl(), hl({ id: 'h1', verse: 17 }), // duplicate id hl({ id: '' }), hl({ id: 'x'.repeat(65) }), hl({ id: 'a', book: 'JOHN' }), hl({ id: 'b', chapter: 0 }), hl({ id: 'c', verse: 201 }), hl({ id: 'd', startOffset: 10, endOffset: 10 }), hl({ id: 'e', startOffset: -1 }), hl({ id: 'f', color: 'yellow' }), 'nope', null, hl({ id: 'ok', book: 'gen' }), ] }); expect(out.highlights.map((h) => h.id)).toEqual(['h1', 'ok']); expect(out.highlights[1].book).toBe('GEN'); // normalised }); test('missing sections become empty ones, and unknown fields are not stored', () => { expect(cleanMarks({})).toEqual({ bookmarks: {}, highlights: [] }); expect(cleanMarks({ highlights: [{ ...hl(), evil: '