147 lines
4.3 KiB
TypeScript
147 lines
4.3 KiB
TypeScript
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<string>()
|
||
|
|
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<string>()
|
||
|
|
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|