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); } }); });