File size: 3,893 Bytes
131b00f
 
 
 
 
 
 
 
 
 
 
 
 
 
7c36fbe
 
131b00f
 
 
 
 
 
 
0a89d37
131b00f
 
 
 
 
 
7c36fbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131b00f
 
 
 
0a89d37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131b00f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { describe, it, expect } from 'vitest';
import { exportSession, importSession } from './session';

const state = {
  fileName: 'm.pdf',
  pdfHash: 'abc123',
  paper: { title: 'T', authors: 'A', venue: '', arxivId: '', pages: 2,
           paragraphs: [{ id: 'p1', section: 'S', firstOfSection: true, text: 'x' }] },
  litPapers: { b1: { id: 'b1', short: 'I22', title: 't', authors: 'a', year: 2022,
                     journal: '', bibcode: 'b1', arxiv: '', abstract: 'ab',
                     scores: { embed: 0.9, keywords: 0.5, rank: 1 } } },
  litByPara: { p1: ['b1'] },
  noteModes: { p1: 'explainer' },
  commentary: { 'p1:explainer': { text: 'note', cites: ['b1'], source: 'llm' } },
  figures: [{ id: 'f1', label: 'Figure 1', caption: 'A diagram', page: 2, hasImage: true }],
  litByFig: { f1: ['b1'] },
};

describe('session', () => {
  it('round-trips and never includes API keys', () => {
    const json = exportSession({ ...state, annotator: { provider: 'anthropic', model: 'm', key: 'SECRET' } });
    expect(json).not.toContain('SECRET');
    const parsed = JSON.parse(json);
    expect(parsed.version).toBe(2);
    expect(parsed.savedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
    const restored = importSession(json);
    expect(restored.paper.paragraphs[0].id).toBe('p1');
    expect(restored.commentary['p1:explainer'].text).toBe('note');
    expect(restored.litByPara.p1).toEqual(['b1']);
  });
  it('round-trips figures and litByFig', () => {
    const json = exportSession(state);
    const restored = importSession(json);
    expect(restored.figures).toHaveLength(1);
    expect(restored.figures[0].id).toBe('f1');
    expect(restored.figures[0].caption).toBe('A diagram');
    expect(restored.litByFig.f1).toEqual(['b1']);
  });
  it('tolerates absence of figures/litByFig in older sessions', () => {
    // Simulate an old session without these fields
    const old = exportSession({ ...state, figures: undefined, litByFig: undefined });
    const parsed = JSON.parse(old);
    // Remove them to simulate an old session file
    delete parsed.figures;
    delete parsed.litByFig;
    const restored = importSession(JSON.stringify(parsed));
    expect(restored.figures).toEqual([]);
    expect(restored.litByFig).toEqual({});
  });
  it('rejects malformed sessions', () => {
    expect(() => importSession('{"version":99}')).toThrow();
    expect(() => importSession('not json')).toThrow();
  });

  it('version 2 exports and imports new fields', () => {
    const json = exportSession({
      ...state,
      equations: [{ id: 'eq1', page: 2, tag: '(3)', afterPara: 'p1', hasImage: true }],
      references: [{ id: 'r1', raw: 'Smith 2020 ApJ 900 1', short: 'Smith 2020', year: 2020,
                     bibcode: null, arxiv: null, corpusMatch: false }],
      citeIndex: { 'smith:2020': ['r1'] },
      userNotes: { p1: { text: 'my note', updatedAt: '2026-06-16T00:00:00Z' } },
      paperNote: 'general note',
    });
    const restored = importSession(json);
    expect(restored.equations).toHaveLength(1);
    expect(restored.references[0].short).toBe('Smith 2020');
    expect(restored.userNotes.p1.text).toBe('my note');
    expect(restored.paperNote).toBe('general note');
  });

  it('v1 session imports tolerantly (all new fields default to empty)', () => {
    const v1json = JSON.stringify({
      version: 1, fileName: 'x.pdf', pdfHash: '',
      paper: { title: 'T', authors: '', venue: '', arxivId: '', pages: 1,
               paragraphs: [{ id: 'p1', section: 'S', firstOfSection: true, text: 'x' }] },
      litPapers: {}, litByPara: {}, noteModes: {}, commentary: {},
    });
    const restored = importSession(v1json);
    expect(restored.equations).toEqual([]);
    expect(restored.references).toEqual([]);
    expect(restored.citeIndex).toEqual({});
    expect(restored.userNotes).toEqual({});
    expect(restored.paperNote).toBe('');
  });
});