test: add 65 server engine tests — answers, scoring, challenges
- answers.test.ts: 19 tests covering all 10 checkAnswer() tiers - scoring.test.ts: 30 tests for scoreRound, calculateElo, calculateTier, applyModifiers - challenges.test.ts: 16 tests for pickChallenge, type exclusion, data integrity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b2d1a2da02
commit
f837153771
@@ -0,0 +1,123 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { checkAnswer } from './answers.js'
|
||||
|
||||
describe('checkAnswer', () => {
|
||||
// --- Tier 1: exact match after normalization ---
|
||||
it('exact match (case insensitive)', () => {
|
||||
expect(checkAnswer('Paris', ['paris'])).toBe(1.0)
|
||||
expect(checkAnswer('PARIS', ['paris'])).toBe(1.0)
|
||||
expect(checkAnswer('paris', ['Paris'])).toBe(1.0)
|
||||
})
|
||||
|
||||
it('exact match strips punctuation', () => {
|
||||
expect(checkAnswer('Paris!', ['paris'])).toBe(1.0)
|
||||
expect(checkAnswer(' Paris ', ['paris'])).toBe(1.0)
|
||||
})
|
||||
|
||||
it('strips leading articles', () => {
|
||||
expect(checkAnswer('the Eiffel Tower', ['Eiffel Tower'])).toBe(1.0)
|
||||
expect(checkAnswer('an apple', ['apple'])).toBe(1.0)
|
||||
expect(checkAnswer('a dog', ['dog'])).toBe(1.0)
|
||||
})
|
||||
|
||||
// --- Tier 2: contraction expansion ---
|
||||
it('contraction expansion', () => {
|
||||
expect(checkAnswer("can't", ['cannot'])).toBe(1.0)
|
||||
expect(checkAnswer("don't", ['do not'])).toBe(1.0)
|
||||
expect(checkAnswer("it's", ['it is'])).toBe(1.0)
|
||||
expect(checkAnswer("they're", ['they are'])).toBe(1.0)
|
||||
})
|
||||
|
||||
// --- Tier 3: stemmed match ---
|
||||
it('stemmed match (plural/singular)', () => {
|
||||
expect(checkAnswer('dogs', ['dog'])).toBe(1.0)
|
||||
expect(checkAnswer('buses', ['bus'])).toBe(1.0)
|
||||
expect(checkAnswer('foxes', ['fox'])).toBe(1.0)
|
||||
})
|
||||
|
||||
// --- Tier 4: numeric equivalence ---
|
||||
it('numeric equivalence', () => {
|
||||
expect(checkAnswer('42', ['42'])).toBe(1.0)
|
||||
expect(checkAnswer('42.0', ['42'])).toBe(1.0)
|
||||
expect(checkAnswer('forty', ['40'])).toBe(1.0)
|
||||
expect(checkAnswer('twenty', ['20'])).toBe(1.0)
|
||||
expect(checkAnswer('one hundred', ['100'])).toBe(1.0)
|
||||
expect(checkAnswer('three hundred', ['300'])).toBe(1.0)
|
||||
})
|
||||
|
||||
it('compound number words', () => {
|
||||
expect(checkAnswer('twenty one', ['21'])).toBe(1.0)
|
||||
expect(checkAnswer('three hundred', ['300'])).toBe(1.0)
|
||||
})
|
||||
|
||||
// --- Tier 5: containment ---
|
||||
it('response contains accepted answer', () => {
|
||||
expect(checkAnswer('The answer is Paris of course', ['paris'])).toBe(1.0)
|
||||
expect(checkAnswer('I believe the answer is 42', ['42'])).toBe(1.0)
|
||||
})
|
||||
|
||||
it('stemmed containment', () => {
|
||||
const score = checkAnswer('There are many dogs in the park', ['dog'])
|
||||
expect(score).toBeGreaterThanOrEqual(0.95)
|
||||
})
|
||||
|
||||
// --- Tier 6: accepted contains response ---
|
||||
it('accepted answer contains the response (short answer)', () => {
|
||||
const score = checkAnswer('Paris', ['The city of Paris'])
|
||||
expect(score).toBeGreaterThanOrEqual(0.75)
|
||||
})
|
||||
|
||||
// --- Tier 7: number in longer response ---
|
||||
it('number embedded in response', () => {
|
||||
expect(checkAnswer('I think it is about 42 meters', ['42'])).toBe(1.0)
|
||||
})
|
||||
|
||||
it('number word in response matches', () => {
|
||||
const score = checkAnswer('I think forty is the answer', ['40'])
|
||||
expect(score).toBeGreaterThanOrEqual(0.9)
|
||||
})
|
||||
|
||||
// --- Tier 8: word-level containment ---
|
||||
it('all words of answer appear in response', () => {
|
||||
const score = checkAnswer('The Great Wall is in China and is very long', ['Great Wall'])
|
||||
expect(score).toBeGreaterThanOrEqual(0.9)
|
||||
})
|
||||
|
||||
// --- Tier 10: true/false ---
|
||||
it('true/false match', () => {
|
||||
expect(checkAnswer('True', ['true'])).toBe(1.0)
|
||||
expect(checkAnswer('true, definitely', ['true'])).toBeGreaterThanOrEqual(0.9)
|
||||
expect(checkAnswer('yes', ['true'])).toBeGreaterThanOrEqual(0.9)
|
||||
expect(checkAnswer('correct', ['true'])).toBeGreaterThanOrEqual(0.9)
|
||||
expect(checkAnswer('no', ['false'])).toBeGreaterThanOrEqual(0.9)
|
||||
expect(checkAnswer('wrong', ['false'])).toBeGreaterThanOrEqual(0.9)
|
||||
expect(checkAnswer('incorrect', ['false'])).toBeGreaterThanOrEqual(0.9)
|
||||
})
|
||||
|
||||
// --- Edge cases ---
|
||||
it('null response returns 0', () => {
|
||||
expect(checkAnswer(null, ['Paris'])).toBe(0)
|
||||
})
|
||||
|
||||
it('empty response returns 0', () => {
|
||||
expect(checkAnswer('', ['Paris'])).toBe(0)
|
||||
expect(checkAnswer(' ', ['Paris'])).toBe(0)
|
||||
})
|
||||
|
||||
it('completely wrong answer returns 0', () => {
|
||||
expect(checkAnswer('banana', ['Paris'])).toBe(0)
|
||||
})
|
||||
|
||||
it('multiple accepted answers', () => {
|
||||
expect(checkAnswer('NYC', ['New York City', 'NYC', 'New York'])).toBe(1.0)
|
||||
// "New York" contained in "New York City" (tier 6: accepted contains response) scores 0.8
|
||||
expect(checkAnswer('New York', ['New York City', 'NYC', 'New York'])).toBeGreaterThanOrEqual(0.8)
|
||||
})
|
||||
|
||||
it('very short accepted answer needs >= 2 chars for containment', () => {
|
||||
// Single char answers shouldn't trigger containment on random text
|
||||
const score = checkAnswer('absolutely nothing relevant', ['a'])
|
||||
// 'a' length is 1, so containment shouldn't trigger (requires >= 2)
|
||||
expect(score).toBeLessThanOrEqual(0.8)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user