Added verse-submission infrastructure

This commit is contained in:
George Powell
2026-07-07 15:41:10 -04:00
parent 99552c57ad
commit be0d7ad297
17 changed files with 1293 additions and 26 deletions
+141
View File
@@ -0,0 +1,141 @@
import { describe, test, expect } from "bun:test";
import {
composeVerseWindow,
formatWindowReference,
getChapterCount,
getVerseCount,
extractVerses,
} from "$lib/server/xml-bible";
import { bookIdToNumber, getBookById } from "$lib/server/bible";
describe("getChapterCount / getVerseCount", () => {
test("Genesis has 50 chapters", () => {
expect(getChapterCount(bookIdToNumber["GEN"])).toBe(50);
});
test("Jude has 1 chapter", () => {
expect(getChapterCount(bookIdToNumber["JUD"])).toBe(1);
});
test("Genesis 1 has 31 verses", () => {
expect(getVerseCount(bookIdToNumber["GEN"], 1)).toBe(31);
});
});
describe("composeVerseWindow — default [v-2, v-1, v]", () => {
test("Genesis 1:3 → window is 1:1-3 (same chapter, hyphen)", () => {
const w = composeVerseWindow(bookIdToNumber["GEN"], 1, 3);
expect(w).not.toBeNull();
expect(w!.startChapter).toBe(1);
expect(w!.startVerse).toBe(1);
expect(w!.endChapter).toBe(1);
expect(w!.endVerse).toBe(3);
expect(w!.verses).toHaveLength(3);
expect(formatWindowReference(w!.bookName, w!.startChapter, w!.startVerse, w!.endChapter, w!.endVerse))
.toBe("Genesis 1:1-3");
});
test("selected verse is the last in the default window", () => {
const w = composeVerseWindow(bookIdToNumber["GEN"], 1, 10);
expect(w!.selectedVerse).toBe(10);
// verses[2] should equal Genesis 1:10's text
const direct = extractVerses(bookIdToNumber["GEN"], 1, 10, 1);
expect(w!.verses[2]).toBe(direct[0]);
});
});
describe("composeVerseWindow — fall-forward at book start", () => {
test("Genesis 1:1 → fall forward to [1,2,3]", () => {
const w = composeVerseWindow(bookIdToNumber["GEN"], 1, 1);
expect(w!.startChapter).toBe(1);
expect(w!.startVerse).toBe(1);
expect(w!.endChapter).toBe(1);
expect(w!.endVerse).toBe(3);
expect(w!.verses).toHaveLength(3);
expect(formatWindowReference(w!.bookName, w!.startChapter, w!.startVerse, w!.endChapter, w!.endVerse))
.toBe("Genesis 1:1-3");
});
test("Genesis 1:2 → fall forward to [2,3,4] (v-2 < 1)", () => {
const w = composeVerseWindow(bookIdToNumber["GEN"], 1, 2);
expect(w!.startVerse).toBe(2);
expect(w!.endVerse).toBe(4);
expect(w!.verses).toHaveLength(3);
});
test("John 1:1 → fall forward", () => {
const w = composeVerseWindow(bookIdToNumber["JHN"], 1, 1);
expect(w!.bookId).toBe("JHN");
expect(w!.startVerse).toBe(1);
expect(w!.endVerse).toBe(3);
});
});
describe("composeVerseWindow — cross-chapter within a book", () => {
test("Genesis 2:1 → window spans 1:30-2:1 (en-dash, two chapters)", () => {
const w = composeVerseWindow(bookIdToNumber["GEN"], 2, 1);
expect(w!.startChapter).toBe(1);
expect(w!.endChapter).toBe(2);
// Genesis 1 has 31 verses; selected index for 2:1 = 31 + 1 = 32; window = [30,31,32]
expect(w!.startVerse).toBe(30);
expect(w!.startChapter === 1 && w!.endChapter === 2).toBe(true);
expect(w!.verses).toHaveLength(3);
const ref = formatWindowReference(w!.bookName, w!.startChapter, w!.startVerse, w!.endChapter, w!.endVerse);
expect(ref).toBe("Genesis 1:30\u20132:1");
});
test("window never crosses a book boundary", () => {
// Every book has >= 3 verses, and windows are constrained in-book by construction.
// Spot-check the last verse of Malachi 4 (end of OT) still resolves in-book.
const mal = bookIdToNumber["MAL"];
const lastChapter = getChapterCount(mal);
const lastVerse = getVerseCount(mal, lastChapter);
const w = composeVerseWindow(mal, lastChapter, lastVerse);
expect(w).not.toBeNull();
expect(w!.bookId).toBe("MAL");
expect(w!.endChapter).toBe(lastChapter);
});
});
describe("composeVerseWindow — invalid input", () => {
test("unknown book number returns null", () => {
expect(composeVerseWindow(999, 1, 1)).toBeNull();
});
test("chapter out of range returns null", () => {
expect(composeVerseWindow(bookIdToNumber["GEN"], 999, 1)).toBeNull();
});
test("verse out of range returns null", () => {
expect(composeVerseWindow(bookIdToNumber["GEN"], 1, 9999)).toBeNull();
});
});
describe("formatWindowReference", () => {
test("same chapter, range", () => {
expect(formatWindowReference("Genesis", 1, 1, 1, 3)).toBe("Genesis 1:1-3");
});
test("same chapter, single verse", () => {
expect(formatWindowReference("Jude", 1, 5, 1, 5)).toBe("Jude 1:5");
});
test("cross chapter uses en-dash", () => {
expect(formatWindowReference("Genesis", 1, 31, 2, 1)).toBe("Genesis 1:31\u20132:1");
});
});
describe("composeVerseWindow — every book's opening verse is safe", () => {
// The spec guarantees every book has >=3 verses after its opening, so Gen 1:1-style
// fall-forward must succeed for the first verse of every book.
test("book 1:1 of all 66 books resolves to a 3-verse in-book window", () => {
const ids = Object.keys(bookIdToNumber);
for (const id of ids) {
const num = bookIdToNumber[id];
const w = composeVerseWindow(num, 1, 1);
expect(w, `${getBookById(id)!.name} 1:1 should resolve`).not.toBeNull();
expect(w!.verses).toHaveLength(3);
expect(w!.bookId).toBe(id);
}
});
});