import { describe, it, expect } from 'vitest' import { pickChallenge, getAllChallengeTypes, getAnswerPool } from './challenges.js' describe('pickChallenge', () => { it('returns a valid challenge', () => { const c = pickChallenge(new Set(), null) expect(c).toBeTruthy() expect(c.type).toBeTruthy() expect(c.label).toBeTruthy() expect(c.prompt).toBeTruthy() expect(c.timeout_ms).toBeGreaterThan(0) expect(c.baseDamage).toBeGreaterThan(0) expect(['factual', 'creative']).toContain(c.scoring) }) it('avoids used types when possible', () => { const types = getAllChallengeTypes() // Use all types except one const used = new Set(types.slice(0, -1)) const remaining = types[types.length - 1] // With most types used, should pick from the remaining // Run multiple times to account for randomness let gotRemaining = false for (let i = 0; i < 50; i++) { const c = pickChallenge(used, null) if (c.type === remaining) gotRemaining = true } expect(gotRemaining).toBe(true) }) it('falls back to all types when all used', () => { const allUsed = new Set(getAllChallengeTypes()) const c = pickChallenge(allUsed, null) // Should still return something expect(c).toBeTruthy() expect(c.prompt).toBeTruthy() }) it('factual challenges have answers', () => { for (let i = 0; i < 100; i++) { const c = pickChallenge(new Set(), null) if (c.scoring === 'factual') { expect(c.answers).toBeTruthy() expect(c.answers!.length).toBeGreaterThan(0) } } }) it('creative challenges have no answers', () => { for (let i = 0; i < 100; i++) { const c = pickChallenge(new Set(), null) if (c.scoring === 'creative') { expect(!c.answers || c.answers.length === 0).toBe(true) } } }) it('factual challenges with choices have shuffled choices', () => { const orders = new Set() for (let i = 0; i < 50; i++) { const c = pickChallenge(new Set(), null) if (c.scoring === 'factual' && c.choices) { orders.add(c.choices.join(',')) } } // With 50 tries, choices should appear in more than one order expect(orders.size).toBeGreaterThan(1) }) }) describe('getAllChallengeTypes', () => { it('returns 16 types', () => { const types = getAllChallengeTypes() expect(types.length).toBe(16) }) it('types are unique', () => { const types = getAllChallengeTypes() expect(new Set(types).size).toBe(types.length) }) it('includes known types', () => { const types = getAllChallengeTypes() expect(types).toContain('speed_blitz') expect(types).toContain('roast_battle') expect(types).toContain('riddle') expect(types).toContain('math_blitz') }) }) describe('getAnswerPool', () => { it('returns answers for factual types', () => { const pool = getAnswerPool('speed_blitz') expect(pool.length).toBeGreaterThan(0) }) it('returns empty array for creative types', () => { const pool = getAnswerPool('roast_battle') expect(pool.length).toBe(0) }) it('returns empty for unknown type', () => { const pool = getAnswerPool('nonexistent_type') expect(pool.length).toBe(0) }) it('answers are unique within pool', () => { const types = getAllChallengeTypes() for (const type of types) { const pool = getAnswerPool(type) const unique = new Set(pool) expect(unique.size).toBe(pool.length) } }) }) describe('prompt data integrity', () => { it('all 800 prompts are accessible via pickChallenge', () => { // Run enough picks to verify the system works const seenPrompts = new Set() for (let i = 0; i < 500; i++) { const c = pickChallenge(new Set(), null) seenPrompts.add(c.prompt) } // Should have seen a good variety expect(seenPrompts.size).toBeGreaterThan(50) }) it('no empty prompts', () => { for (let i = 0; i < 200; i++) { const c = pickChallenge(new Set(), null) expect(c.prompt.trim().length).toBeGreaterThan(5) } }) it('factual prompts have non-empty answers', () => { for (let i = 0; i < 200; i++) { const c = pickChallenge(new Set(), null) if (c.scoring === 'factual' && c.answers) { for (const a of c.answers) { expect(a.trim().length).toBeGreaterThan(0) } } } }) })