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>
340 lines
9.4 KiB
TypeScript
340 lines
9.4 KiB
TypeScript
import { describe, test, expect } from "bun:test";
|
|
import { generateShareText, getVerseSnippet } from "$lib/utils/share";
|
|
import { getBookById } from "$lib/utils/game";
|
|
import type { Guess } from "$lib/utils/game";
|
|
|
|
// Helpers to build Guess objects without calling evaluateGuess
|
|
function makeGuess(bookId: string, overrides: Partial<Omit<Guess, "book">> = {}): Guess {
|
|
const book = getBookById(bookId)!;
|
|
return {
|
|
book,
|
|
testamentMatch: false,
|
|
sectionMatch: false,
|
|
adjacent: false,
|
|
firstLetterMatch: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
const CORRECT_BOOK_ID = "GEN";
|
|
|
|
const exactGuess = makeGuess("GEN", {
|
|
testamentMatch: true,
|
|
sectionMatch: true,
|
|
});
|
|
|
|
const adjacentGuess = makeGuess("EXO", {
|
|
testamentMatch: true,
|
|
sectionMatch: true,
|
|
adjacent: true,
|
|
});
|
|
|
|
const sectionGuess = makeGuess("LEV", {
|
|
testamentMatch: true,
|
|
sectionMatch: true,
|
|
});
|
|
|
|
const testamentGuess = makeGuess("JOS", {
|
|
testamentMatch: true,
|
|
sectionMatch: false,
|
|
});
|
|
|
|
const noMatchGuess = makeGuess("MAT", {
|
|
testamentMatch: false,
|
|
sectionMatch: false,
|
|
});
|
|
|
|
describe("generateShareText — emoji mapping", () => {
|
|
test("exact match → ✅", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "In the beginning...",
|
|
});
|
|
expect(text).toContain("✅");
|
|
});
|
|
|
|
test("adjacent book → ‼️", () => {
|
|
const text = generateShareText({
|
|
guesses: [adjacentGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "In the beginning...",
|
|
});
|
|
expect(text).toContain("‼️");
|
|
});
|
|
|
|
test("section match → 🟩", () => {
|
|
// LEV matches section (Law) but is not adjacent to GEN (order 1 vs 3)
|
|
const text = generateShareText({
|
|
guesses: [sectionGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "In the beginning...",
|
|
});
|
|
expect(text).toContain("🟩");
|
|
});
|
|
|
|
test("testament match only → 🟧", () => {
|
|
const text = generateShareText({
|
|
guesses: [testamentGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "In the beginning...",
|
|
});
|
|
expect(text).toContain("🟧");
|
|
});
|
|
|
|
test("no match → 🟥", () => {
|
|
const text = generateShareText({
|
|
guesses: [noMatchGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "In the beginning...",
|
|
});
|
|
expect(text).toContain("🟥");
|
|
});
|
|
});
|
|
|
|
describe("generateShareText — guess count wording", () => {
|
|
test("1 guess uses singular 'guess'", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).toContain("1 guess,");
|
|
});
|
|
|
|
test("multiple guesses uses plural 'guesses'", () => {
|
|
const text = generateShareText({
|
|
guesses: [noMatchGuess, testamentGuess, exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).toContain("3 guesses,");
|
|
});
|
|
});
|
|
|
|
describe("generateShareText — streak display", () => {
|
|
test("streak > 1 is shown with fire emoji", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
streak: 5,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).toContain("5 days 🔥");
|
|
});
|
|
|
|
test("streak of 1 is not shown", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
streak: 1,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).not.toContain("🔥");
|
|
});
|
|
|
|
test("undefined streak is not shown", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).not.toContain("🔥");
|
|
});
|
|
});
|
|
|
|
describe("generateShareText — chapter star", () => {
|
|
test("1 guess + chapterCorrect → ⭐ appended to emoji line", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: true,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).toContain("✅ ⭐");
|
|
});
|
|
|
|
test("multiple guesses + chapterCorrect → no star (only awarded for hole-in-one)", () => {
|
|
const text = generateShareText({
|
|
guesses: [noMatchGuess, exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: true,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).not.toContain("⭐");
|
|
});
|
|
|
|
test("1 guess + chapterCorrect false → no star", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).not.toContain("⭐");
|
|
});
|
|
});
|
|
|
|
describe("generateShareText — login book emoji", () => {
|
|
test("logged in uses 📜", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: true,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).toContain("📜");
|
|
expect(text).not.toContain("📖");
|
|
});
|
|
|
|
test("not logged in uses 📖", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).toContain("📖");
|
|
expect(text).not.toContain("📜");
|
|
});
|
|
});
|
|
|
|
describe("generateShareText — date formatting", () => {
|
|
test("date is formatted as 'Mon DD, YYYY'", () => {
|
|
const text = generateShareText({
|
|
guesses: [exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
expect(text).toContain("Jan 15, 2025");
|
|
});
|
|
});
|
|
|
|
describe("generateShareText — guess order", () => {
|
|
test("guesses are reversed in the emoji line (first guess last)", () => {
|
|
// noMatchGuess first, then exactGuess — reversed output: ✅🟥
|
|
const text = generateShareText({
|
|
guesses: [noMatchGuess, exactGuess],
|
|
correctBookId: CORRECT_BOOK_ID,
|
|
dailyVerseDate: "2025-01-15",
|
|
chapterCorrect: false,
|
|
isLoggedIn: false,
|
|
origin: "https://bibdle.com",
|
|
verseText: "...",
|
|
});
|
|
const lines = text.split("\n");
|
|
expect(lines[2]).toBe("✅🟥");
|
|
});
|
|
});
|
|
|
|
describe("getVerseSnippet", () => {
|
|
test("wraps output in curly double quotes", () => {
|
|
const result = getVerseSnippet("Hello world");
|
|
expect(result.startsWith("\u201C")).toBe(true);
|
|
expect(result.endsWith("\u201D")).toBe(true);
|
|
});
|
|
|
|
test("short verse (fewer than 10 words) returns full text", () => {
|
|
const result = getVerseSnippet("For God so loved");
|
|
// No punctuation search happens, returns all words
|
|
expect(result).toContain("For God so loved");
|
|
expect(result).toContain("...");
|
|
});
|
|
|
|
test("verse with no punctuation in range returns first 25 words", () => {
|
|
const words = Array.from({ length: 30 }, (_, i) => `word${i + 1}`);
|
|
const verse = words.join(" ");
|
|
const result = getVerseSnippet(verse);
|
|
// Should contain up to 25 words
|
|
expect(result).toContain("word25");
|
|
expect(result).not.toContain("word26");
|
|
});
|
|
|
|
test("truncates at punctuation between words 10 and 25", () => {
|
|
// 12 words before comma, rest after
|
|
const verse =
|
|
"one two three four five six seven eight nine ten eleven twelve, thirteen fourteen fifteen twenty";
|
|
const result = getVerseSnippet(verse);
|
|
// The comma is after word 12, which is between word 10 and 25
|
|
expect(result).toContain("twelve");
|
|
expect(result).not.toContain("thirteen");
|
|
});
|
|
|
|
test("punctuation before word 10 does not trigger truncation", () => {
|
|
// Comma is after word 5 — before the search window starts at word 10
|
|
const verse =
|
|
"one two three four five, six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen";
|
|
const result = getVerseSnippet(verse);
|
|
// The comma at word 5 is before start of search range, so we continue
|
|
// The snippet should contain word 10 at minimum
|
|
expect(result).toContain("ten");
|
|
});
|
|
|
|
test("does not include trailing whitespace before ellipsis", () => {
|
|
const verse =
|
|
"one two three four five six seven eight nine ten eleven twelve, rest of verse here";
|
|
const result = getVerseSnippet(verse);
|
|
// trimEnd is applied before adding ..., so no space before ...
|
|
expect(result).not.toMatch(/\s\.\.\./);
|
|
});
|
|
});
|