mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
146 tests covering evaluateGuess, grading, ordinals, bible data integrity, section counts, share text generation, and stat string helpers. Also fixes toOrdinal for 111-113 (was using >= 11 && <= 13 instead of % 100 check). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
272 lines
8.8 KiB
TypeScript
272 lines
8.8 KiB
TypeScript
import { describe, test, expect } from "bun:test";
|
|
import {
|
|
evaluateGuess,
|
|
getBookById,
|
|
getFirstLetter,
|
|
getGrade,
|
|
getNextGradeMessage,
|
|
isAdjacent,
|
|
toOrdinal,
|
|
} from "$lib/utils/game";
|
|
|
|
describe("getBookById", () => {
|
|
test("returns correct book for a valid ID", () => {
|
|
const book = getBookById("GEN");
|
|
expect(book).toBeDefined();
|
|
expect(book!.name).toBe("Genesis");
|
|
expect(book!.order).toBe(1);
|
|
});
|
|
|
|
test("returns the last book by ID", () => {
|
|
const book = getBookById("REV");
|
|
expect(book).toBeDefined();
|
|
expect(book!.name).toBe("Revelation");
|
|
expect(book!.order).toBe(66);
|
|
});
|
|
|
|
test("returns undefined for an invalid ID", () => {
|
|
expect(getBookById("INVALID")).toBeUndefined();
|
|
});
|
|
|
|
test("returns undefined for an empty string", () => {
|
|
expect(getBookById("")).toBeUndefined();
|
|
});
|
|
|
|
test("is case-sensitive", () => {
|
|
expect(getBookById("gen")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("isAdjacent", () => {
|
|
test("consecutive books are adjacent", () => {
|
|
expect(isAdjacent("GEN", "EXO")).toBe(true); // 1, 2
|
|
});
|
|
|
|
test("adjacency is symmetric", () => {
|
|
expect(isAdjacent("EXO", "GEN")).toBe(true);
|
|
});
|
|
|
|
test("books two apart are not adjacent", () => {
|
|
expect(isAdjacent("GEN", "LEV")).toBe(false); // 1, 3
|
|
});
|
|
|
|
test("the same book is not adjacent to itself", () => {
|
|
expect(isAdjacent("GEN", "GEN")).toBe(false); // diff = 0
|
|
});
|
|
|
|
test("works across testament boundary (Malachi / Matthew)", () => {
|
|
expect(isAdjacent("MAL", "MAT")).toBe(true); // 39, 40
|
|
});
|
|
|
|
test("far-apart books are not adjacent", () => {
|
|
expect(isAdjacent("GEN", "REV")).toBe(false);
|
|
});
|
|
|
|
test("returns false for unknown IDs", () => {
|
|
expect(isAdjacent("FAKE", "GEN")).toBe(false);
|
|
expect(isAdjacent("GEN", "FAKE")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getFirstLetter", () => {
|
|
test("returns first letter of a normal book name", () => {
|
|
expect(getFirstLetter("Genesis")).toBe("G");
|
|
expect(getFirstLetter("Revelation")).toBe("R");
|
|
});
|
|
|
|
test("skips leading digits and returns first letter", () => {
|
|
expect(getFirstLetter("1 Samuel")).toBe("S");
|
|
expect(getFirstLetter("2 Kings")).toBe("K");
|
|
expect(getFirstLetter("1 Corinthians")).toBe("C");
|
|
expect(getFirstLetter("3 John")).toBe("J");
|
|
});
|
|
|
|
test("returns first letter of multi-word names", () => {
|
|
expect(getFirstLetter("Song of Solomon")).toBe("S");
|
|
});
|
|
});
|
|
|
|
describe("evaluateGuess", () => {
|
|
test("returns null for an invalid guess ID", () => {
|
|
expect(evaluateGuess("INVALID", "GEN")).toBeNull();
|
|
});
|
|
|
|
test("returns null for an invalid correct ID", () => {
|
|
expect(evaluateGuess("GEN", "INVALID")).toBeNull();
|
|
});
|
|
|
|
test("exact book match: testamentMatch and sectionMatch are true, adjacent is false", () => {
|
|
const result = evaluateGuess("GEN", "GEN");
|
|
expect(result).not.toBeNull();
|
|
expect(result!.book.id).toBe("GEN");
|
|
expect(result!.testamentMatch).toBe(true);
|
|
expect(result!.sectionMatch).toBe(true);
|
|
expect(result!.adjacent).toBe(false);
|
|
});
|
|
|
|
test("same section implies same testament", () => {
|
|
// Genesis and Exodus are both OT Law
|
|
const result = evaluateGuess("GEN", "EXO");
|
|
expect(result!.testamentMatch).toBe(true);
|
|
expect(result!.sectionMatch).toBe(true);
|
|
expect(result!.adjacent).toBe(true);
|
|
});
|
|
|
|
test("same testament, different section", () => {
|
|
// Genesis (Law) vs Joshua (History) — both OT
|
|
const result = evaluateGuess("GEN", "JOS");
|
|
expect(result!.testamentMatch).toBe(true);
|
|
expect(result!.sectionMatch).toBe(false);
|
|
expect(result!.adjacent).toBe(false);
|
|
});
|
|
|
|
test("different testament, no match", () => {
|
|
// Genesis (OT) vs Matthew (NT)
|
|
const result = evaluateGuess("GEN", "MAT");
|
|
expect(result!.testamentMatch).toBe(false);
|
|
expect(result!.sectionMatch).toBe(false);
|
|
expect(result!.adjacent).toBe(false);
|
|
});
|
|
|
|
test("adjacent books across testament boundary", () => {
|
|
// Malachi (OT, 39) and Matthew (NT, 40)
|
|
const result = evaluateGuess("MAL", "MAT");
|
|
expect(result!.adjacent).toBe(true);
|
|
expect(result!.testamentMatch).toBe(false);
|
|
expect(result!.sectionMatch).toBe(false);
|
|
});
|
|
|
|
test("adjacent books within same testament and section", () => {
|
|
// Hosea (28) and Joel (29), both Minor Prophets
|
|
const result = evaluateGuess("HOS", "JOL");
|
|
expect(result!.adjacent).toBe(true);
|
|
expect(result!.testamentMatch).toBe(true);
|
|
expect(result!.sectionMatch).toBe(true);
|
|
});
|
|
|
|
test("firstLetterMatch: same first letter", () => {
|
|
// Genesis and Galatians both start with G
|
|
const result = evaluateGuess("GEN", "GAL");
|
|
expect(result!.firstLetterMatch).toBe(true);
|
|
});
|
|
|
|
test("firstLetterMatch: different first letter", () => {
|
|
// Genesis (G) vs Matthew (M)
|
|
const result = evaluateGuess("GEN", "MAT");
|
|
expect(result!.firstLetterMatch).toBe(false);
|
|
});
|
|
|
|
test("firstLetterMatch is case-insensitive", () => {
|
|
// Both start with J but from different contexts — Jeremiah vs Joel
|
|
const result = evaluateGuess("JER", "JOL");
|
|
expect(result!.firstLetterMatch).toBe(true);
|
|
});
|
|
|
|
test("special case: two Epistle '1' books always firstLetterMatch", () => {
|
|
// 1 Corinthians (Pauline) vs 1 John (General) — both Epistles starting with "1"
|
|
const result = evaluateGuess("1CO", "1JN");
|
|
expect(result!.firstLetterMatch).toBe(true);
|
|
});
|
|
|
|
test("special case: Epistle '1' book vs non-Epistle '1' book — no special treatment", () => {
|
|
// 1 Corinthians (Pauline Epistles) vs 1 Samuel (History)
|
|
// Correct is NOT Epistles, so special case doesn't apply
|
|
const result = evaluateGuess("1CO", "1SA");
|
|
// getFirstLetter("1 Corinthians") = "C", getFirstLetter("1 Samuel") = "S" → false
|
|
expect(result!.firstLetterMatch).toBe(false);
|
|
});
|
|
|
|
test("special case only triggers when BOTH are Epistle '1' books", () => {
|
|
// 2 Corinthians (Pauline, starts with "2") vs 1 John (General, starts with "1")
|
|
// guessIsEpistlesWithNumber requires name[0] === "1", so 2CO fails
|
|
const result = evaluateGuess("2CO", "1JN");
|
|
// getFirstLetter("2 Corinthians") = "C", getFirstLetter("1 John") = "J" → false
|
|
expect(result!.firstLetterMatch).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getGrade", () => {
|
|
test("1 guess → S+", () => expect(getGrade(1)).toBe("S+"));
|
|
test("2 guesses → A+", () => expect(getGrade(2)).toBe("A+"));
|
|
test("3 guesses → A", () => expect(getGrade(3)).toBe("A"));
|
|
test("4 guesses → B+", () => expect(getGrade(4)).toBe("B+"));
|
|
test("6 guesses → B+", () => expect(getGrade(6)).toBe("B+"));
|
|
test("7 guesses → B", () => expect(getGrade(7)).toBe("B"));
|
|
test("10 guesses → B", () => expect(getGrade(10)).toBe("B"));
|
|
test("11 guesses → C+", () => expect(getGrade(11)).toBe("C+"));
|
|
test("15 guesses → C+", () => expect(getGrade(15)).toBe("C+"));
|
|
test("16 guesses → C", () => expect(getGrade(16)).toBe("C"));
|
|
test("100 guesses → C", () => expect(getGrade(100)).toBe("C"));
|
|
});
|
|
|
|
describe("getNextGradeMessage", () => {
|
|
test("returns empty string at top grade", () => {
|
|
expect(getNextGradeMessage(1)).toBe("");
|
|
});
|
|
|
|
test("grade A+ shows 1 guess threshold", () => {
|
|
expect(getNextGradeMessage(2)).toBe("Next grade: 1 guess or less");
|
|
});
|
|
|
|
test("grade A shows 2 guess threshold", () => {
|
|
expect(getNextGradeMessage(3)).toBe("Next grade: 2 guesses or less");
|
|
});
|
|
|
|
test("grade B+ shows 3 guess threshold", () => {
|
|
expect(getNextGradeMessage(4)).toBe("Next grade: 3 guesses or less");
|
|
expect(getNextGradeMessage(6)).toBe("Next grade: 3 guesses or less");
|
|
});
|
|
|
|
test("grade B shows 6 guess threshold", () => {
|
|
expect(getNextGradeMessage(7)).toBe("Next grade: 6 guesses or less");
|
|
expect(getNextGradeMessage(10)).toBe("Next grade: 6 guesses or less");
|
|
});
|
|
|
|
test("grade C+ shows 10 guess threshold", () => {
|
|
expect(getNextGradeMessage(11)).toBe("Next grade: 10 guesses or less");
|
|
expect(getNextGradeMessage(15)).toBe("Next grade: 10 guesses or less");
|
|
});
|
|
|
|
test("grade C shows 15 guess threshold", () => {
|
|
expect(getNextGradeMessage(16)).toBe("Next grade: 15 guesses or less");
|
|
expect(getNextGradeMessage(50)).toBe("Next grade: 15 guesses or less");
|
|
});
|
|
});
|
|
|
|
describe("toOrdinal", () => {
|
|
test("1st, 2nd, 3rd", () => {
|
|
expect(toOrdinal(1)).toBe("1st");
|
|
expect(toOrdinal(2)).toBe("2nd");
|
|
expect(toOrdinal(3)).toBe("3rd");
|
|
});
|
|
|
|
test("4-10 use th", () => {
|
|
expect(toOrdinal(4)).toBe("4th");
|
|
expect(toOrdinal(10)).toBe("10th");
|
|
});
|
|
|
|
test("11, 12, 13 use th (not st/nd/rd)", () => {
|
|
expect(toOrdinal(11)).toBe("11th");
|
|
expect(toOrdinal(12)).toBe("12th");
|
|
expect(toOrdinal(13)).toBe("13th");
|
|
});
|
|
|
|
test("21, 22, 23 use st/nd/rd", () => {
|
|
expect(toOrdinal(21)).toBe("21st");
|
|
expect(toOrdinal(22)).toBe("22nd");
|
|
expect(toOrdinal(23)).toBe("23rd");
|
|
});
|
|
|
|
test("101, 102, 103 use st/nd/rd", () => {
|
|
expect(toOrdinal(101)).toBe("101st");
|
|
expect(toOrdinal(102)).toBe("102nd");
|
|
expect(toOrdinal(103)).toBe("103rd");
|
|
});
|
|
|
|
test("111, 112, 113 use th", () => {
|
|
expect(toOrdinal(111)).toBe("111th");
|
|
expect(toOrdinal(112)).toBe("112th");
|
|
expect(toOrdinal(113)).toBe("113th");
|
|
});
|
|
});
|