Spaces:
Sleeping
Sleeping
| import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; | |
| import { CrowdPulseService } from '../src/services/crowdPulse.js'; | |
| // Mock fetch globally | |
| global.fetch = vi.fn(); | |
| // Mock localStorage | |
| const localStorageMock = (() => { | |
| let store = {}; | |
| return { | |
| getItem: vi.fn((key) => store[key] || null), | |
| setItem: vi.fn((key, value) => { store[key] = String(value); }), | |
| removeItem: vi.fn((key) => { delete store[key]; }), | |
| clear: vi.fn(() => { store = {}; }), | |
| }; | |
| })(); | |
| global.localStorage = localStorageMock; | |
| describe('CrowdPulseService', () => { | |
| let service; | |
| beforeEach(() => { | |
| service = new CrowdPulseService(); | |
| vi.clearAllMocks(); | |
| localStorageMock.clear(); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| }); | |
| describe('Question generation', () => { | |
| it('should generate questions that reference grounded events', () => { | |
| const events = [ | |
| { type: 'goal', player: 'Salah', minute: 22, team: 'Liverpool' }, | |
| { type: 'yellow_card', player: 'Bruno', minute: 30, team: 'Man United' }, | |
| ]; | |
| const question = service.generateQuestion(events); | |
| expect(question).toBeDefined(); | |
| expect(question.text).toBeDefined(); | |
| expect(question.id).toBeDefined(); | |
| // Question should reference something from the events | |
| const questionReferencesEvent = events.some( | |
| (e) => | |
| question.text.includes(e.player) || | |
| question.text.includes(e.team) || | |
| question.groundedEvent !== undefined | |
| ); | |
| expect(questionReferencesEvent || question.groundedEvent).toBeTruthy(); | |
| }); | |
| it('should return null when no events provided', () => { | |
| const question = service.generateQuestion([]); | |
| expect(question).toBeNull(); | |
| }); | |
| it('should include answer options in generated question', () => { | |
| const events = [ | |
| { type: 'goal', player: 'Kane', minute: 15, team: 'Bayern' }, | |
| ]; | |
| const question = service.generateQuestion(events); | |
| expect(question.options).toBeDefined(); | |
| expect(question.options.length).toBeGreaterThanOrEqual(2); | |
| }); | |
| }); | |
| describe('Answer submission', () => { | |
| it('should record correct answer submission', () => { | |
| const questionId = 'q-001'; | |
| service.setActiveQuestion({ | |
| id: questionId, | |
| text: 'Who scored?', | |
| correctAnswer: 'Salah', | |
| options: ['Salah', 'Mane', 'Firmino'], | |
| }); | |
| const result = service.submitAnswer(questionId, 'Salah'); | |
| expect(result.correct).toBe(true); | |
| expect(result.pointsAwarded).toBeGreaterThan(0); | |
| }); | |
| it('should record incorrect answer submission', () => { | |
| const questionId = 'q-002'; | |
| service.setActiveQuestion({ | |
| id: questionId, | |
| text: 'Who got the yellow card?', | |
| correctAnswer: 'Bruno', | |
| options: ['Bruno', 'Rashford', 'Shaw'], | |
| }); | |
| const result = service.submitAnswer(questionId, 'Rashford'); | |
| expect(result.correct).toBe(false); | |
| }); | |
| it('should reject answer for unknown question ID', () => { | |
| const result = service.submitAnswer('nonexistent-q', 'SomeAnswer'); | |
| expect(result.error).toBeDefined(); | |
| }); | |
| }); | |
| describe('Leaderboard scoring', () => { | |
| it('should award +10 points for correct answer', () => { | |
| const questionId = 'q-010'; | |
| service.setActiveQuestion({ | |
| id: questionId, | |
| text: 'Who scored the penalty?', | |
| correctAnswer: 'Kane', | |
| options: ['Kane', 'Saka', 'Sterling'], | |
| }); | |
| const initialScore = service.getScore(); | |
| service.submitAnswer(questionId, 'Kane'); | |
| const newScore = service.getScore(); | |
| expect(newScore - initialScore).toBe(10); | |
| }); | |
| it('should apply streak bonus for consecutive correct answers', () => { | |
| // Answer 3 questions correctly in a row | |
| const questions = [ | |
| { id: 'q-s1', text: 'Q1?', correctAnswer: 'A', options: ['A', 'B'] }, | |
| { id: 'q-s2', text: 'Q2?', correctAnswer: 'B', options: ['A', 'B'] }, | |
| { id: 'q-s3', text: 'Q3?', correctAnswer: 'A', options: ['A', 'B'] }, | |
| ]; | |
| const scores = []; | |
| questions.forEach((q) => { | |
| service.setActiveQuestion(q); | |
| const result = service.submitAnswer(q.id, q.correctAnswer); | |
| scores.push(result.pointsAwarded); | |
| }); | |
| // Third answer should have streak bonus (more than base 10) | |
| expect(scores[2]).toBeGreaterThan(scores[0]); | |
| }); | |
| it('should reset streak on incorrect answer', () => { | |
| // Correct, correct, wrong, correct | |
| const questions = [ | |
| { id: 'q-r1', text: 'Q1?', correctAnswer: 'A', options: ['A', 'B'] }, | |
| { id: 'q-r2', text: 'Q2?', correctAnswer: 'B', options: ['A', 'B'] }, | |
| { id: 'q-r3', text: 'Q3?', correctAnswer: 'A', options: ['A', 'B'] }, | |
| { id: 'q-r4', text: 'Q4?', correctAnswer: 'B', options: ['A', 'B'] }, | |
| ]; | |
| service.setActiveQuestion(questions[0]); | |
| service.submitAnswer('q-r1', 'A'); // correct | |
| service.setActiveQuestion(questions[1]); | |
| service.submitAnswer('q-r2', 'B'); // correct | |
| service.setActiveQuestion(questions[2]); | |
| service.submitAnswer('q-r3', 'B'); // wrong — streak resets | |
| service.setActiveQuestion(questions[3]); | |
| const result = service.submitAnswer('q-r4', 'B'); // correct, but streak=1 | |
| // Points should be base (10), no streak bonus | |
| expect(result.pointsAwarded).toBe(10); | |
| }); | |
| }); | |
| describe('History tracking', () => { | |
| it('should track past questions in history', () => { | |
| service.setActiveQuestion({ | |
| id: 'q-h1', | |
| text: 'Who scored?', | |
| correctAnswer: 'Messi', | |
| options: ['Messi', 'Neymar'], | |
| }); | |
| service.submitAnswer('q-h1', 'Messi'); | |
| const history = service.getHistory(); | |
| expect(history).toHaveLength(1); | |
| expect(history[0].questionId).toBe('q-h1'); | |
| expect(history[0].userAnswer).toBe('Messi'); | |
| expect(history[0].correct).toBe(true); | |
| }); | |
| it('should maintain order in history', () => { | |
| const questions = [ | |
| { id: 'q-o1', text: 'Q1?', correctAnswer: 'A', options: ['A', 'B'] }, | |
| { id: 'q-o2', text: 'Q2?', correctAnswer: 'B', options: ['A', 'B'] }, | |
| ]; | |
| questions.forEach((q) => { | |
| service.setActiveQuestion(q); | |
| service.submitAnswer(q.id, q.correctAnswer); | |
| }); | |
| const history = service.getHistory(); | |
| expect(history[0].questionId).toBe('q-o1'); | |
| expect(history[1].questionId).toBe('q-o2'); | |
| }); | |
| }); | |
| describe('Active question management', () => { | |
| it('should set and retrieve active question', () => { | |
| const question = { | |
| id: 'q-a1', | |
| text: 'Next scorer?', | |
| correctAnswer: 'Haaland', | |
| options: ['Haaland', 'De Bruyne', 'Foden'], | |
| }; | |
| service.setActiveQuestion(question); | |
| const active = service.getActiveQuestion(); | |
| expect(active).toBeDefined(); | |
| expect(active.id).toBe('q-a1'); | |
| }); | |
| it('should clear active question after submission', () => { | |
| service.setActiveQuestion({ | |
| id: 'q-c1', | |
| text: 'Who got red?', | |
| correctAnswer: 'Ramos', | |
| options: ['Ramos', 'Pique'], | |
| }); | |
| service.submitAnswer('q-c1', 'Ramos'); | |
| const active = service.getActiveQuestion(); | |
| expect(active).toBeNull(); | |
| }); | |
| }); | |
| }); | |