Files
study-bible-project/src/utils/recall.test.js
T
nmemmertandClaude Sonnet 5.5 0c8a632cff Add own-work tracking, Claude hand-off check, and weekly recall
- Long pastes into Observe/Interpret/Apply are tagged as pasted; notes show
  Mine / N pasted and an own-words share. Pastes follow text through merges.
- The "-> Claude" buttons on the Script stage ask first when a chunk lacks
  enough of the user's own (non-pasted) words.
- Weekly recall card on Home: write a passage from memory, compare with notes,
  self-rate; recalls are saved on the chunk and synced.
- Recall history on Home with an eight-week strip and the full list.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 12:25:49 -04:00

59 lines
2.8 KiB
JavaScript

import { test, expect } from 'vitest';
import { pickRecall, WEEK_MS } from './recall.js';
const NOW = 100 * WEEK_MS;
const text = (n) => Array.from({ length: n }, (_, i) => `w${i}`).join(' ');
const chunk = (id, extra = {}) => ({ id, startVerse: 1, endVerse: 5, observation: text(40), editedAt: NOW - 2 * WEEK_MS, ...extra });
const study = (chunks, id = 'p1') => ({ id, title: 'Romans', translation: 'BSB', chapters: [{ book: 'Romans', bookAbbrev: 'Rom', chapter: 8, chunks }] });
test('picks a studied chunk that has enough of your own writing', () => {
const pick = pickRecall([study([chunk('a')])], NOW);
expect(pick).toMatchObject({ projectId: 'p1', projectTitle: 'Romans' });
expect(pick.chunk.id).toBe('a');
});
test('skips thin notes, pasted notes and chunks edited this week', () => {
const pasted = text(40);
const projects = [study([
chunk('thin', { observation: text(10) }),
chunk('pasted', { observation: pasted, aiPastes: { observation: [pasted] } }),
chunk('fresh', { editedAt: NOW - 1000 }),
])];
expect(pickRecall(projects, NOW)).toBeNull();
});
test('never-recalled chunks come before ones recalled long ago', () => {
const old = chunk('old', { recalls: [{ at: NOW - 3 * WEEK_MS, text: 'x', rating: 'solid' }] });
expect(pickRecall([study([old, chunk('new')])], NOW).chunk.id).toBe('new');
});
test('one recall a week: nothing is offered after you have done one', () => {
const done = chunk('done', { recalls: [{ at: NOW - 1000, text: 'x', rating: 'partly' }] });
expect(pickRecall([study([done, chunk('other')])], NOW)).toBeNull();
expect(pickRecall([study([done, chunk('other')])], NOW + WEEK_MS)).not.toBeNull();
});
import { recallHistory, recallSummary } from './recall.js';
test('history lists every recall newest first, with where it came from', () => {
const a = chunk('a', { recalls: [{ at: NOW - 3 * WEEK_MS, text: 'old', rating: 'forgot' }, { at: NOW - 1000, text: 'new', rating: 'solid' }] });
const h = recallHistory([study([a])]);
expect(h.map((x) => x.text)).toEqual(['new', 'old']);
expect(h[0]).toMatchObject({ projectTitle: 'Romans', chunkId: 'a' });
expect(h[0].reference).toContain('Romans');
});
test('the summary counts ratings and marks each of the last eight weeks by its weakest recall', () => {
const history = [
{ at: NOW - 1000, rating: 'solid' }, { at: NOW - 2000, rating: 'partly' },
{ at: NOW - 2 * WEEK_MS + 1000, rating: 'forgot' },
];
const { counts, total, recent } = recallSummary(history, NOW);
expect(counts).toEqual({ solid: 1, partly: 1, forgot: 1 });
expect(total).toBe(3);
expect(recent).toHaveLength(8);
expect(recent[7].rating).toBe('partly'); // this week: the weaker of solid and partly
expect(recent[6].rating).toBe('forgot'); // last week
expect(recent[0].rating).toBeNull();
});