Teaching tools: word occurrences, blanks handout, templates, series progress, book info, comments
Build and Push Docker Image / build-and-push (push) Successful in 6m41s

- Words tab: "Where else" lists every verse using a Strong's number, by book
  (new /api/strongs/:number/occurrences, indexed from the interlinear files)
- Handout: fill-in-the-blanks layout with a word bank
- Four new study templates: character, topical, word, verse by verse
- Finish: series progress (not started / studied / ready / taught)
- Background: book at a glance for all 66 books
- Chunk comments for co-teachers, merged between copies
- TODO: correct BSB audio note, tick co-teacher comments

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-27 15:44:37 -04:00
1 parent d9891ff9fe
commit a6af9bd1c6
24 files changed
+670 -9

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
import { test, expect } from 'vitest';
import { indexBook, normalizeStrongs, occurrences } from './strongs.js';
test('normalizeStrongs accepts the usual spellings and rejects the rest', () => {
expect(normalizeStrongs('g4102')).toBe('G4102');
expect(normalizeStrongs(' H07225 ')).toBe('H7225');
expect(normalizeStrongs('H1234a')).toBe('H1234');
expect(normalizeStrongs('4102')).toBeNull();
expect(normalizeStrongs('')).toBeNull();
expect(normalizeStrongs(undefined)).toBeNull();
});
test('occurrences groups verses by book and counts a verse once however often the word repeats', () => {
const index = new Map();
indexBook(index, 'JHN', {
3: { 16: [{ s: 'G25' }, { s: 'G25' }, { s: 'G2316' }], 17: [{ s: 'G2316' }] },
1: { 1: [{ s: 'G3056' }, { s: 'G2316' }] },
});
indexBook(index, 'ROM', { 5: { 8: [{ s: 'G25' }, { s: 'G2316' }] } });
const love = occurrences(index, 'g25');
expect(love.total).toBe(2);
expect(love.books.map((b) => [b.book, b.name, b.count])).toEqual([['JHN', 'John', 1], ['ROM', 'Romans', 1]]);
const god = occurrences(index, 'G2316');
expect(god.total).toBe(4);
expect(god.books[0].refs).toEqual([[1, 1], [3, 16], [3, 17]]); // sorted by chapter then verse
});
test('an unknown or invalid number comes back empty', () => {
const index = new Map();
expect(occurrences(index, 'G99999')).toEqual({ strongs: 'G99999', total: 0, books: [] });
expect(occurrences(index, 'nonsense')).toEqual({ strongs: null, total: 0, books: [] });
});
test('malformed interlinear data is skipped, not thrown', () => {
const index = new Map();
expect(() => indexBook(index, 'GEN', { 1: { 1: null, 2: 'x', 3: [null, { s: 5 }, { s: 'H430' }] } })).not.toThrow();
expect(occurrences(index, 'H430').total).toBe(1);
});