mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-06 01:43:32 -04:00
add unit tests for core game, bible, share, and stats utilities
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>
This commit is contained in:
271
tests/game.test.ts
Normal file
271
tests/game.test.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user