import { test, expect } from 'vitest'; import { weeklySummary, themeIndex, wordIndex, slippedList, followUpList } from './insights.js'; import { 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(20), editedAt: NOW - 1000, ...extra }); const study = (chunks, id = 'p1', title = 'Romans') => ({ id, title, translation: 'BSB', chapters: [{ book: 'Romans', bookAbbrev: 'Rom', chapter: 8, chunks }] }); test('the weekly summary counts only chunks edited this week', () => { const pasted = text(10); const projects = [study([ chunk('a', { teachBack: 'x', recalls: [{ at: NOW - 1000, rating: 'solid' }] }), chunk('b', { observation: `${text(10)} ${pasted}`, aiPastes: { observation: [pasted] }, editedAt: NOW - 500 }), chunk('old', { editedAt: NOW - 3 * WEEK_MS, recalls: [{ at: NOW - 3 * WEEK_MS, rating: 'solid' }] }), ])]; const s = weeklySummary(projects, NOW); expect(s).toMatchObject({ chunksTouched: 2, ownWords: 30, recalls: 1, teachBacks: 1 }); expect(s.ownShare).toBe(75); // 100% and 50%, averaged expect(weeklySummary([], NOW).ownShare).toBeNull(); }); test('tags group notes from different studies into themes, most used first', () => { const p1 = study([chunk('a', { tags: ['Grace'] }), chunk('b', { tags: ['hope'] })]); const p2 = study([chunk('c', { tags: ['grace '] })], 'p2', 'John'); const themes = themeIndex([p1, p2]); expect(themes.map((t) => [t.tag, t.count])).toEqual([['Grace', 2], ['hope', 1]]); expect(themes[0].items.map((i) => i.projectTitle)).toEqual(['Romans', 'John']); }); test('words that come up in more than one place are listed', () => { const w = (n, g) => ({ id: n, strongNumber: n, lexeme: g, englishGloss: g }); const projects = [study([chunk('a', { greekWords: [w('G26', 'agape'), w('G4102', 'pistis')] }), chunk('b', { greekWords: [w('g26', 'agape')] })])]; const list = wordIndex(projects); expect(list).toHaveLength(1); expect(list[0]).toMatchObject({ strong: 'G26', count: 2 }); expect(wordIndex(projects, { minCount: 1 })).toHaveLength(2); }); test('slipped lines and follow-ups are collected, open follow-ups first', () => { const projects = [study([chunk('a', { recalls: [{ at: 5, gaps: '- the serpent\nwho spoke', rating: 'partly' }, { at: 9, gaps: '', rating: 'solid' }], followUps: [{ id: '1', text: 'done thing', done: true }, { id: '2', text: 'call Sam', done: false }], })])]; expect(slippedList(projects)).toHaveLength(1); expect(slippedList(projects)[0].items).toEqual(['the serpent', 'who spoke']); expect(followUpList(projects).map((f) => f.text)).toEqual(['call Sam', 'done thing']); }); test('five-minute sessions this week are counted', () => { const p = [study([chunk('a', { fiveMinutes: [{ at: NOW - 1000, field: 'observation' }, { at: NOW - 3 * WEEK_MS, field: 'application' }] })])]; expect(weeklySummary(p, NOW).fiveMinutes).toBe(1); });