feat(rsvp): sync reading position to cloud via book_configs (#3801)

This commit is contained in:
Lex Moulton
2026-04-09 09:52:49 -04:00
committed by GitHub
parent 1e259e87b2
commit baee85e7c8
8 changed files with 120 additions and 6 deletions
@@ -1,7 +1,12 @@
import { describe, it, expect } from 'vitest';
import { transformBookNoteToDB, transformBookNoteFromDB } from '@/utils/transform';
import { BookNote } from '@/types/book';
import { DBBookNote } from '@/types/records';
import {
transformBookNoteToDB,
transformBookNoteFromDB,
transformBookConfigToDB,
transformBookConfigFromDB,
} from '@/utils/transform';
import { BookConfig, BookNote } from '@/types/book';
import { DBBookConfig, DBBookNote } from '@/types/records';
describe('transformBookNoteToDB with xpointer fields', () => {
it('passes through xpointer0 and xpointer1', () => {
@@ -112,3 +117,59 @@ describe('transformBookNoteFromDB with xpointer fields', () => {
expect(note.xpointer1).toBe('/body/DocFragment[1]/body/p[1]/text().20');
});
});
describe('transformBookConfigToDB / transformBookConfigFromDB rsvpPosition', () => {
const baseConfig: BookConfig = {
bookHash: 'hash1',
updatedAt: 1700000000000,
};
it('serializes rsvpPosition to JSON string in DB record', () => {
const config: BookConfig = {
...baseConfig,
rsvpPosition: { cfi: 'epubcfi(/6/4!/4/2/1:0)', wordText: 'hello' },
};
const db = transformBookConfigToDB(config, 'user1');
expect(db.rsvp_position).toBe(
JSON.stringify({ cfi: 'epubcfi(/6/4!/4/2/1:0)', wordText: 'hello' }),
);
});
it('omits rsvp_position when rsvpPosition is undefined', () => {
const db = transformBookConfigToDB(baseConfig, 'user1');
expect(db.rsvp_position).toBeUndefined();
});
it('deserializes rsvp_position from DB record', () => {
const dbConfig: DBBookConfig = {
user_id: 'user1',
book_hash: 'hash1',
rsvp_position: JSON.stringify({ cfi: 'epubcfi(/6/4!/4/2/1:0)', wordText: 'hello' }),
updated_at: '2023-11-14T22:13:20.000Z',
};
const config = transformBookConfigFromDB(dbConfig);
expect(config.rsvpPosition).toEqual({ cfi: 'epubcfi(/6/4!/4/2/1:0)', wordText: 'hello' });
});
it('leaves rsvpPosition undefined when rsvp_position is absent from DB', () => {
const dbConfig: DBBookConfig = {
user_id: 'user1',
book_hash: 'hash1',
updated_at: '2023-11-14T22:13:20.000Z',
};
const config = transformBookConfigFromDB(dbConfig);
expect(config.rsvpPosition).toBeUndefined();
});
it('round-trips rsvpPosition through DB transform', () => {
const config: BookConfig = {
...baseConfig,
rsvpPosition: { cfi: 'epubcfi(/6/8!/4/2/3:5)', wordText: 'world' },
};
const db = transformBookConfigToDB(config, 'user1');
// Simulate what DB returns (updated_at as ISO string)
const dbRecord: DBBookConfig = { ...db, updated_at: new Date(config.updatedAt).toISOString() };
const restored = transformBookConfigFromDB(dbRecord);
expect(restored.rsvpPosition).toEqual(config.rsvpPosition);
});
});