Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- This file is a merged representation of the entire codebase, combined into a single document by Repomix.
- <file_summary>
- This section contains a summary of this file.
- <purpose>
- This file contains a packed representation of the entire repository's contents.
- It is designed to be easily consumable by AI systems for analysis, code review,
- or other automated processes.
- </purpose>
- <file_format>
- The content is organized as follows:
- 1. This summary section
- 2. Repository information
- 3. Directory structure
- 4. Repository files (if enabled)
- 5. Multiple file entries, each consisting of:
- - File path as an attribute
- - Full contents of the file
- </file_format>
- <usage_guidelines>
- - This file should be treated as read-only. Any changes should be made to the
- original repository files, not this packed version.
- - When processing this file, use the file path to distinguish
- between different files in the repository.
- - Be aware that this file may contain sensitive information. Handle it with
- the same level of security as you would the original repository.
- </usage_guidelines>
- <notes>
- - Some files may have been excluded based on .gitignore rules and Repomix's configuration
- - Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- - Files matching patterns in .gitignore are excluded
- - Files matching default ignore patterns are excluded
- - Files are sorted by Git change count (files with more changes are at the bottom)
- </notes>
- </file_summary>
- <directory_structure>
- GameContext.test.tsx
- gamesModalHooks.test.tsx
- gameUtils.test.ts
- itemRegistry.test.ts
- quizForm.test.tsx
- quizIntegration.test.ts
- teamTriviaReducer.test.ts
- </directory_structure>
- <files>
- This section contains the contents of the repository's files.
- <file path="GameContext.test.tsx">
- import { describe, test, expect } from 'vitest';
- import { renderHook, act } from '@testing-library/react';
- import { ReactNode } from 'react';
- import { GameProvider, useGameContext, validateGameState } from '../GameContext';
- describe('GameContext', () => {
- const wrapper = ({ children }: { children: ReactNode }) => (
- <GameProvider>{children}</GameProvider>
- );
- describe('Initial State', () => {
- test('Should initialize with default values', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- expect(result.current.activeGame).toBe(null);
- expect(result.current.numberOfTeams).toBe(0);
- expect(result.current.numberOfQuestions).toBe(0);
- expect(result.current.scores).toEqual({});
- expect(result.current.endGameAfterRound).toBe(false);
- expect(result.current.fontSizeMultiplier).toBe(1.7);
- expect(result.current.teams).toEqual([]);
- });
- });
- describe('State Updates', () => {
- test('Should update active game', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- act(() => {
- result.current.setActiveGame('team-trivia');
- });
- expect(result.current.activeGame).toBe('team-trivia');
- act(() => {
- result.current.setActiveGame(null);
- });
- expect(result.current.activeGame).toBe(null);
- });
- test('Should update number of teams', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- act(() => {
- result.current.setNumberOfTeams(4);
- });
- expect(result.current.numberOfTeams).toBe(4);
- });
- test('Should update number of questions', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- act(() => {
- result.current.setNumberOfQuestions(10);
- });
- expect(result.current.numberOfQuestions).toBe(10);
- });
- test('Should update scores', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- const newScores = {
- team1: 10,
- team2: 20,
- };
- act(() => {
- result.current.setScores(newScores);
- });
- expect(result.current.scores).toEqual(newScores);
- });
- test('Should update endGameAfterRound', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- act(() => {
- result.current.setEndGameAfterRound(true);
- });
- expect(result.current.endGameAfterRound).toBe(true);
- });
- test('Should update font size multiplier', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- act(() => {
- result.current.setFontSizeMultiplier(2.5);
- });
- expect(result.current.fontSizeMultiplier).toBe(2.5);
- });
- test('Should update teams', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- const newTeams = [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- { id: 1, name: 'Team 2', score: 20, color: 'red' },
- ];
- act(() => {
- result.current.setTeams(newTeams);
- });
- expect(result.current.teams).toEqual(newTeams);
- });
- });
- describe('Multiple Updates', () => {
- test('Should handle multiple state updates', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- act(() => {
- result.current.setActiveGame('team-trivia');
- result.current.setNumberOfTeams(3);
- result.current.setNumberOfQuestions(15);
- result.current.setFontSizeMultiplier(2.0);
- });
- expect(result.current.activeGame).toBe('team-trivia');
- expect(result.current.numberOfTeams).toBe(3);
- expect(result.current.numberOfQuestions).toBe(15);
- expect(result.current.fontSizeMultiplier).toBe(2.0);
- });
- test('Should preserve other state when updating one value', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- act(() => {
- result.current.setNumberOfTeams(4);
- result.current.setNumberOfQuestions(10);
- });
- act(() => {
- result.current.setActiveGame('team-trivia');
- });
- expect(result.current.numberOfTeams).toBe(4);
- expect(result.current.numberOfQuestions).toBe(10);
- expect(result.current.activeGame).toBe('team-trivia');
- });
- });
- describe('Teams and Scores Integration', () => {
- test('Should handle team and score updates together', () => {
- const { result } = renderHook(() => useGameContext(), { wrapper });
- const teams = [
- { id: 0, name: 'Team A', score: 0, color: 'blue' },
- { id: 1, name: 'Team B', score: 0, color: 'red' },
- ];
- act(() => {
- result.current.setTeams(teams);
- });
- const scores = {
- 'Team A': 15,
- 'Team B': 25,
- };
- act(() => {
- result.current.setScores(scores);
- });
- expect(result.current.teams).toEqual(teams);
- expect(result.current.scores).toEqual(scores);
- });
- });
- describe('Error Handling', () => {
- test('Should throw error when useGameContext is used outside provider', () => {
- // Suppress console.error for this test
- const originalError = console.error;
- console.error = () => {};
- expect(() => {
- renderHook(() => useGameContext());
- }).toThrow('useGameContext must be used within a GameProvider');
- console.error = originalError;
- });
- });
- });
- describe('validateGameState', () => {
- test('Should validate number of teams', () => {
- const result1 = validateGameState({ numberOfTeams: 2 });
- expect(result1.isValid).toBe(true);
- expect(result1.errors).toHaveLength(0);
- const result2 = validateGameState({ numberOfTeams: 1 });
- expect(result2.isValid).toBe(false);
- expect(result2.errors).toContain('Number of teams must be at least 2');
- const result3 = validateGameState({ numberOfTeams: 0 });
- expect(result3.isValid).toBe(false);
- expect(result3.errors).toContain('Number of teams must be at least 2');
- });
- test('Should validate number of questions', () => {
- const result1 = validateGameState({ numberOfQuestions: 1 });
- expect(result1.isValid).toBe(true);
- expect(result1.errors).toHaveLength(0);
- const result2 = validateGameState({ numberOfQuestions: 0 });
- expect(result2.isValid).toBe(false);
- expect(result2.errors).toContain('Number of questions must be at least 1');
- const result3 = validateGameState({ numberOfQuestions: -5 });
- expect(result3.isValid).toBe(false);
- expect(result3.errors).toContain('Number of questions must be at least 1');
- });
- test('Should validate font size multiplier', () => {
- const result1 = validateGameState({ fontSizeMultiplier: 1.5 });
- expect(result1.isValid).toBe(true);
- expect(result1.errors).toHaveLength(0);
- const result2 = validateGameState({ fontSizeMultiplier: 0.3 });
- expect(result2.isValid).toBe(false);
- expect(result2.errors).toContain('Font size multiplier must be between 0.5 and 5');
- const result3 = validateGameState({ fontSizeMultiplier: 6 });
- expect(result3.isValid).toBe(false);
- expect(result3.errors).toContain('Font size multiplier must be between 0.5 and 5');
- });
- test('Should validate multiple fields', () => {
- const result = validateGameState({
- numberOfTeams: 1,
- numberOfQuestions: 0,
- fontSizeMultiplier: 10,
- });
- expect(result.isValid).toBe(false);
- expect(result.errors).toHaveLength(3);
- expect(result.errors).toContain('Number of teams must be at least 2');
- expect(result.errors).toContain('Number of questions must be at least 1');
- expect(result.errors).toContain('Font size multiplier must be between 0.5 and 5');
- });
- test('Should pass validation with all valid values', () => {
- const result = validateGameState({
- numberOfTeams: 4,
- numberOfQuestions: 10,
- fontSizeMultiplier: 2.0,
- });
- expect(result.isValid).toBe(true);
- expect(result.errors).toHaveLength(0);
- });
- test('Should handle partial state validation', () => {
- const result1 = validateGameState({ numberOfTeams: 3 });
- expect(result1.isValid).toBe(true);
- const result2 = validateGameState({ fontSizeMultiplier: 1.8 });
- expect(result2.isValid).toBe(true);
- });
- test('Should handle empty state', () => {
- const result = validateGameState({});
- expect(result.isValid).toBe(true);
- expect(result.errors).toHaveLength(0);
- });
- });
- </file>
- <file path="gamesModalHooks.test.tsx">
- import { describe, test, expect, vi, beforeEach } from 'vitest';
- import { renderHook, act } from '@testing-library/react';
- import { server } from '~/test-utils/mocks/server';
- import { errorHandlers } from '~/test-utils/mocks/handlers';
- import { useQuizFetchers } from '../GamesModal/hooks';
- import type { Quiz, ClientBox, TempImage } from '~/lib/types';
- // Mock the React Router hooks
- vi.mock('react-router', () => ({
- useFetcher: vi.fn(() => ({
- state: 'idle',
- data: null,
- submit: vi.fn(),
- })),
- }));
- // Mock the stores
- vi.mock('~/stores/lessonStore', () => ({
- useLessonStore: vi.fn(() => vi.fn()),
- }));
- // Mock error context
- vi.mock('../../errorAndLog/errorAndLogContext', () => ({
- useLogAndError: () => ({
- setError: vi.fn(),
- logEvent: vi.fn(),
- }),
- }));
- describe('GamesModal Hooks Integration Tests with MSW', () => {
- const defaultProps = {
- lessonId: 'test-lesson-123',
- localQuiz: null,
- editorContent: '',
- hasChanges: false,
- boxes: [] as ClientBox[],
- tempImages: [] as TempImage[],
- includeKnowledgeBase: false,
- includeNotes: false,
- includeCurrentContent: false,
- questionCount: 5,
- onQuizSave: vi.fn(),
- onQuizDelete: vi.fn(),
- setIsSubmitting: vi.fn(),
- setIsDeleting: vi.fn(),
- setIsSaving: vi.fn(),
- };
- beforeEach(() => {
- vi.clearAllMocks();
- server.resetHandlers();
- });
- describe('Quiz Generation with MSW', () => {
- test('Should successfully generate quiz with MSW mock', async () => {
- // The MSW handler will automatically intercept the request
- // and return a mock quiz based on our handlers
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- defaultProps.localQuiz,
- 'Test content for quiz generation',
- defaultProps.hasChanges,
- defaultProps.boxes,
- defaultProps.tempImages,
- true, // includeKnowledgeBase
- false,
- false,
- 10, // questionCount
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- // Since MSW will intercept the actual HTTP request,
- // we can test that the handler returns the expected data structure
- expect(result.current.handleGenerateQuiz).toBeDefined();
- });
- test('Should handle quiz generation error with MSW', async () => {
- // Override the default handler with an error handler
- server.use(errorHandlers.quizGenerationError);
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- defaultProps.localQuiz,
- 'Test content',
- defaultProps.hasChanges,
- defaultProps.boxes,
- defaultProps.tempImages,
- true,
- false,
- false,
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- expect(result.current.handleGenerateQuiz).toBeDefined();
- });
- test('Should handle network errors with MSW', async () => {
- // Override with network error handler
- server.use(errorHandlers.networkError);
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- defaultProps.localQuiz,
- 'Test content',
- defaultProps.hasChanges,
- defaultProps.boxes,
- defaultProps.tempImages,
- false,
- false,
- true,
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- expect(result.current.handleGenerateQuiz).toBeDefined();
- });
- });
- describe('Quiz Saving with MSW', () => {
- test('Should successfully save quiz with MSW mock', async () => {
- const existingQuiz: Quiz = {
- id: 'existing-quiz',
- multiple_choice: [
- {
- text: 'Question 1',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- },
- ],
- created_at: new Date().toISOString(),
- };
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- existingQuiz,
- '',
- true, // hasChanges
- defaultProps.boxes,
- defaultProps.tempImages,
- false,
- false,
- false,
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- expect(result.current.handleSaveChanges).toBeDefined();
- });
- test('Should handle save error with MSW', async () => {
- server.use(errorHandlers.quizSaveError);
- const existingQuiz: Quiz = {
- id: 'existing-quiz',
- multiple_choice: [],
- created_at: new Date().toISOString(),
- };
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- existingQuiz,
- '',
- true,
- defaultProps.boxes,
- defaultProps.tempImages,
- false,
- false,
- false,
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- expect(result.current.handleSaveChanges).toBeDefined();
- });
- });
- describe('Quiz Deletion with MSW', () => {
- test('Should successfully delete quiz with MSW mock', async () => {
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- defaultProps.localQuiz,
- '',
- false,
- defaultProps.boxes,
- defaultProps.tempImages,
- false,
- false,
- false,
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- expect(result.current.executeResetQuiz).toBeDefined();
- });
- test('Should handle deletion error with MSW', async () => {
- server.use(errorHandlers.quizDeleteError);
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- defaultProps.localQuiz,
- '',
- false,
- defaultProps.boxes,
- defaultProps.tempImages,
- false,
- false,
- false,
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- expect(result.current.executeResetQuiz).toBeDefined();
- });
- });
- describe('Edge Cases with MSW', () => {
- test('Should validate required parameters before API call', () => {
- const { result } = renderHook(() =>
- useQuizFetchers(
- '', // Empty lessonId
- null,
- '',
- false,
- defaultProps.boxes,
- defaultProps.tempImages,
- false,
- false,
- false,
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- act(() => {
- result.current.handleGenerateQuiz();
- });
- // Should not make API call with invalid parameters
- expect(result.current.handleGenerateQuiz).toBeDefined();
- });
- test('Should handle invalid question count', () => {
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- null,
- 'content',
- false,
- defaultProps.boxes,
- defaultProps.tempImages,
- false,
- false,
- false,
- 0, // Invalid question count
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- act(() => {
- result.current.handleGenerateQuiz();
- });
- expect(result.current.handleGenerateQuiz).toBeDefined();
- });
- test('Should handle no content selected', () => {
- const { result } = renderHook(() =>
- useQuizFetchers(
- defaultProps.lessonId,
- null,
- '', // No editor content
- false,
- [], // No boxes
- [], // No images
- false, // No knowledge base
- false, // No notes
- false, // No current content
- 5,
- defaultProps.onQuizSave,
- defaultProps.onQuizDelete,
- defaultProps.setIsSubmitting,
- defaultProps.setIsDeleting,
- defaultProps.setIsSaving
- )
- );
- act(() => {
- result.current.handleGenerateQuiz();
- });
- expect(result.current.handleGenerateQuiz).toBeDefined();
- });
- });
- });
- </file>
- <file path="gameUtils.test.ts">
- import { describe, test, expect } from 'vitest';
- import { shuffleArray, generateTeamColors } from '../TeamTrivia/lib/utils';
- import { TEAM_COLORS } from '../TeamTrivia/lib/constants';
- describe('Game Utilities', () => {
- describe('shuffleArray', () => {
- test('Should return array of same length', () => {
- const original = [1, 2, 3, 4, 5];
- const shuffled = shuffleArray(original);
- expect(shuffled).toHaveLength(original.length);
- });
- test('Should contain all original elements', () => {
- const original = ['a', 'b', 'c', 'd'];
- const shuffled = shuffleArray(original);
- original.forEach(item => {
- expect(shuffled).toContain(item);
- });
- });
- test('Should not modify original array', () => {
- const original = [1, 2, 3, 4, 5];
- const originalCopy = [...original];
- shuffleArray(original);
- expect(original).toEqual(originalCopy);
- });
- test('Should produce different orders (probabilistic)', () => {
- const original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
- const results = new Set();
- // Run shuffle multiple times
- for (let i = 0; i < 20; i++) {
- const shuffled = shuffleArray(original);
- results.add(shuffled.join(','));
- }
- // Should have produced at least 2 different orderings
- expect(results.size).toBeGreaterThan(1);
- });
- test('Should handle empty array', () => {
- const empty: number[] = [];
- const shuffled = shuffleArray(empty);
- expect(shuffled).toEqual([]);
- });
- test('Should handle single-element array', () => {
- const single = ['only'];
- const shuffled = shuffleArray(single);
- expect(shuffled).toEqual(['only']);
- });
- test('Should work with objects', () => {
- const objects = [
- { id: 1, name: 'A' },
- { id: 2, name: 'B' },
- { id: 3, name: 'C' },
- ];
- const shuffled = shuffleArray(objects);
- expect(shuffled).toHaveLength(3);
- objects.forEach(obj => {
- expect(shuffled).toContainEqual(obj);
- });
- });
- });
- describe('generateTeamColors', () => {
- test('Should generate correct number of colors', () => {
- const colors = generateTeamColors(4);
- expect(colors).toHaveLength(4);
- });
- test('Should use predefined colors in order', () => {
- const colors = generateTeamColors(3);
- expect(colors[0]).toBe(TEAM_COLORS[0]);
- expect(colors[1]).toBe(TEAM_COLORS[1]);
- expect(colors[2]).toBe(TEAM_COLORS[2]);
- });
- test('Should cycle through colors when more teams than colors', () => {
- const teamCount = TEAM_COLORS.length + 3;
- const colors = generateTeamColors(teamCount);
- expect(colors).toHaveLength(teamCount);
- // Check cycling
- expect(colors[TEAM_COLORS.length]).toBe(TEAM_COLORS[0]);
- expect(colors[TEAM_COLORS.length + 1]).toBe(TEAM_COLORS[1]);
- expect(colors[TEAM_COLORS.length + 2]).toBe(TEAM_COLORS[2]);
- });
- test('Should handle zero teams', () => {
- const colors = generateTeamColors(0);
- expect(colors).toEqual([]);
- });
- test('Should handle one team', () => {
- const colors = generateTeamColors(1);
- expect(colors).toHaveLength(1);
- expect(colors[0]).toBe(TEAM_COLORS[0]);
- });
- test('Should handle large number of teams', () => {
- const colors = generateTeamColors(50);
- expect(colors).toHaveLength(50);
- // All should be valid colors from the palette
- colors.forEach((color, index) => {
- const expectedColor = TEAM_COLORS[index % TEAM_COLORS.length];
- expect(color).toBe(expectedColor);
- });
- });
- test('Should always return string colors', () => {
- const colors = generateTeamColors(5);
- colors.forEach(color => {
- expect(typeof color).toBe('string');
- expect(color.length).toBeGreaterThan(0);
- });
- });
- });
- describe('Integration of utilities', () => {
- test('Shuffled team colors should maintain all unique colors', () => {
- const colors = generateTeamColors(TEAM_COLORS.length);
- const shuffledColors = shuffleArray(colors);
- // Should have all unique colors
- const uniqueColors = new Set(shuffledColors);
- expect(uniqueColors.size).toBe(TEAM_COLORS.length);
- // All original colors should be present
- TEAM_COLORS.forEach(color => {
- expect(shuffledColors).toContain(color);
- });
- });
- });
- });
- </file>
- <file path="itemRegistry.test.ts">
- import { describe, test, expect, beforeEach } from 'vitest';
- import {
- doublePointsItem,
- pointStealItem,
- swapPointsItem,
- givePointsItem,
- twoOptionsItem,
- skipItem,
- gambleItem,
- calculateItemCount,
- assignItemsToQuestions,
- AVAILABLE_ITEMS,
- setActiveItems,
- configureActiveItems,
- getItemById,
- ITEM_REGISTRY,
- ITEM_CATEGORIES,
- } from '../TeamTrivia/lib/itemRegistry';
- import type { GameQuestion, ItemDensity, GameStateBase } from '../TeamTrivia/lib/types';
- describe('Item Registry - Power-Up Definitions', () => {
- describe('Item Properties', () => {
- test('Double Points item should have correct properties', () => {
- expect(doublePointsItem.id).toBe('double-points');
- expect(doublePointsItem.name).toBe('Double Points');
- expect(doublePointsItem.category).toBe('yellow');
- expect(doublePointsItem.imageUrl).toBe('⭐');
- expect(doublePointsItem.description).toContain('double points');
- });
- test('Point Steal item should have correct properties', () => {
- expect(pointStealItem.id).toBe('point-steal');
- expect(pointStealItem.name).toBe('Point Steal');
- expect(pointStealItem.category).toBe('purple');
- expect(pointStealItem.imageUrl).toBe('⚡');
- expect(pointStealItem.description).toContain('steal');
- });
- test('Swap Points item should have correct properties', () => {
- expect(swapPointsItem.id).toBe('swap-points');
- expect(swapPointsItem.name).toBe('Swap Points');
- expect(swapPointsItem.category).toBe('purple');
- expect(swapPointsItem.imageUrl).toBe('🔄');
- expect(swapPointsItem.description).toContain('Exchange');
- });
- test('Give Points item should have correct properties', () => {
- expect(givePointsItem.id).toBe('give-points');
- expect(givePointsItem.name).toBe('Give Points');
- expect(givePointsItem.category).toBe('orange');
- expect(givePointsItem.imageUrl).toBe('🎁');
- expect(givePointsItem.description).toContain('Give');
- });
- test('Two Options item should have correct properties', () => {
- expect(twoOptionsItem.id).toBe('two-options');
- expect(twoOptionsItem.name).toBe('Two Options');
- expect(twoOptionsItem.category).toBe('green');
- expect(twoOptionsItem.imageUrl).toBe('✌️');
- expect(twoOptionsItem.description).toContain('Removes two incorrect');
- });
- test('Skip item should have correct properties', () => {
- expect(skipItem.id).toBe('skip');
- expect(skipItem.name).toBe('Skip Team');
- expect(skipItem.category).toBe('red');
- expect(skipItem.imageUrl).toBe('⏭️');
- expect(skipItem.description).toContain('skip');
- });
- test('Gamble item should have correct properties', () => {
- expect(gambleItem.id).toBe('gamble');
- expect(gambleItem.name).toBe('Gamble');
- expect(gambleItem.category).toBe('orange');
- expect(gambleItem.imageUrl).toBe('🎲');
- expect(gambleItem.description).toContain('Bet');
- });
- });
- describe('Item Effects', () => {
- const mockGameState: GameStateBase = {
- teams: [
- { id: 0, name: 'Team 1', score: 20, color: 'blue' },
- { id: 1, name: 'Team 2', score: 30, color: 'red' },
- ],
- currentTeamIndex: 0,
- };
- test('Double Points effect should return correct multiplier', () => {
- const effect = doublePointsItem.effect(mockGameState);
- expect(effect.multiplyPoints).toBe(2);
- expect(effect.pointsToAward).toBe(20);
- });
- test('Point Steal effect should return steal amount', () => {
- const effect = pointStealItem.effect(mockGameState);
- expect(effect.stealPointsAmount).toBe(5);
- expect(effect.requiresTeamSelection).toBe(true);
- });
- test('Swap Points effect should require team selection', () => {
- const effect = swapPointsItem.effect(mockGameState);
- expect(effect.swapPointsAmount).toBe(true);
- expect(effect.requiresTeamSelection).toBe(true);
- });
- test('Give Points effect should return give amount', () => {
- const effect = givePointsItem.effect(mockGameState);
- expect(effect.givePointsAmount).toBe(5);
- expect(effect.requiresTeamSelection).toBe(true);
- });
- test('Two Options effect should return options to remove', () => {
- const effect = twoOptionsItem.effect(mockGameState);
- expect(effect.optionsToRemove).toBe(2);
- });
- test('Skip effect should return skip turn flag', () => {
- const effect = skipItem.effect(mockGameState);
- expect(effect.skipTurn).toBe(true);
- expect(effect.requiresTeamSelection).toBe(true);
- });
- test('Gamble effect should return gamble flag', () => {
- const effect = gambleItem.effect(mockGameState);
- expect(effect.gamble).toBe(true);
- });
- });
- });
- describe('Item Count Calculation', () => {
- test('Should return 0 for "none" density', () => {
- expect(calculateItemCount('none', 10)).toBe(0);
- });
- test('Should return at least 1 for "a-little" density', () => {
- expect(calculateItemCount('a-little', 5)).toBe(1);
- expect(calculateItemCount('a-little', 10)).toBe(1);
- expect(calculateItemCount('a-little', 20)).toBe(2);
- });
- test('Should return at least 2 for "some" density', () => {
- expect(calculateItemCount('some', 5)).toBe(2);
- expect(calculateItemCount('some', 10)).toBe(3);
- expect(calculateItemCount('some', 20)).toBe(5);
- });
- test('Should return at least 3 for "a-lot" density', () => {
- expect(calculateItemCount('a-lot', 5)).toBe(3);
- expect(calculateItemCount('a-lot', 10)).toBe(4);
- expect(calculateItemCount('a-lot', 20)).toBe(8);
- });
- test('Should handle edge cases', () => {
- expect(calculateItemCount('none', 0)).toBe(0);
- expect(calculateItemCount('a-little', 1)).toBe(1);
- expect(calculateItemCount('invalid' as ItemDensity, 10)).toBe(0);
- });
- });
- describe('Item Assignment to Questions', () => {
- let mockQuestions: Partial<GameQuestion>[];
- beforeEach(() => {
- mockQuestions = Array(10).fill(null).map((_, i) => ({
- text: `Question ${i}`,
- item: null,
- }));
- });
- test('Should not assign items with "none" density', () => {
- const result = assignItemsToQuestions(
- mockQuestions as GameQuestion[],
- 'none'
- );
- const itemCount = result.filter(q => q.item !== null).length;
- expect(itemCount).toBe(0);
- });
- test('Should assign correct number of items for "a-little" density', () => {
- const result = assignItemsToQuestions(
- mockQuestions as GameQuestion[],
- 'a-little'
- );
- const itemCount = result.filter(q => q.item !== null).length;
- expect(itemCount).toBeGreaterThanOrEqual(1);
- expect(itemCount).toBeLessThanOrEqual(2);
- });
- test('Should assign items randomly', () => {
- // Run multiple times to check randomness
- const results = [];
- for (let i = 0; i < 5; i++) {
- const result = assignItemsToQuestions(
- mockQuestions as GameQuestion[],
- 'some'
- );
- const indices = result
- .map((q, i) => q.item ? i : -1)
- .filter(i => i !== -1);
- results.push(indices.join(','));
- }
- // At least some results should be different
- const uniqueResults = new Set(results);
- expect(uniqueResults.size).toBeGreaterThan(1);
- });
- test('Should not assign Swap Points in first round', () => {
- const questions = Array(4).fill(null).map((_, i) => ({
- text: `Question ${i}`,
- item: null,
- }));
- // Force using only swap points item
- const itemsToUse = [swapPointsItem];
- const result = assignItemsToQuestions(
- questions as GameQuestion[],
- 'a-lot',
- itemsToUse,
- 4 // 4 teams means first 4 questions are round 1
- );
- // No swap points should be in first 4 questions
- const firstRoundItems = result.slice(0, 4).filter(q =>
- q.item && q.item.name === 'Swap Points'
- );
- expect(firstRoundItems.length).toBe(0);
- });
- test('Should handle empty item array', () => {
- const result = assignItemsToQuestions(
- mockQuestions as GameQuestion[],
- 'some',
- [] // Empty items array
- );
- const itemCount = result.filter(q => q.item !== null).length;
- expect(itemCount).toBe(0);
- });
- test('Should give each item instance a unique ID', () => {
- const result = assignItemsToQuestions(
- mockQuestions as GameQuestion[],
- 'a-lot'
- );
- const itemIds = result
- .filter(q => q.item !== null)
- .map(q => q.item!.id);
- const uniqueIds = new Set(itemIds);
- expect(uniqueIds.size).toBe(itemIds.length);
- });
- });
- describe('Active Items Configuration', () => {
- test('setActiveItems should update AVAILABLE_ITEMS', () => {
- setActiveItems(['double-points', 'point-steal']);
- expect(AVAILABLE_ITEMS).toHaveLength(2);
- expect(AVAILABLE_ITEMS[0]?.id).toBe('double-points');
- expect(AVAILABLE_ITEMS[1]?.id).toBe('point-steal');
- // Reset
- setActiveItems(['double-points', 'point-steal', 'swap-points', 'give-points', 'two-options']);
- });
- test('configureActiveItems should update based on flags', () => {
- configureActiveItems({
- doublePoints: true,
- pointSteal: false,
- swapPoints: true,
- givePoints: false,
- twoOptions: true,
- skip: false,
- gamble: false,
- });
- const activeIds = AVAILABLE_ITEMS.map(item => item.id);
- expect(activeIds).toContain('double-points');
- expect(activeIds).not.toContain('point-steal');
- expect(activeIds).toContain('swap-points');
- expect(activeIds).not.toContain('give-points');
- expect(activeIds).toContain('two-options');
- // Reset
- configureActiveItems({
- doublePoints: true,
- pointSteal: true,
- swapPoints: true,
- givePoints: true,
- twoOptions: true,
- skip: false,
- gamble: false,
- });
- });
- test('getItemById should return correct item', () => {
- expect(getItemById('double-points')).toBe(doublePointsItem);
- expect(getItemById('point-steal')).toBe(pointStealItem);
- expect(getItemById('invalid-id')).toBeUndefined();
- });
- });
- describe('Item Categories', () => {
- test('ITEM_CATEGORIES should group items correctly', () => {
- expect(ITEM_CATEGORIES.green).toContain(twoOptionsItem);
- expect(ITEM_CATEGORIES.yellow).toContain(doublePointsItem);
- expect(ITEM_CATEGORIES.red).toContain(skipItem);
- expect(ITEM_CATEGORIES.orange).toContain(givePointsItem);
- expect(ITEM_CATEGORIES.orange).toContain(gambleItem);
- expect(ITEM_CATEGORIES.purple).toContain(pointStealItem);
- expect(ITEM_CATEGORIES.purple).toContain(swapPointsItem);
- });
- test('All items should be in ITEM_REGISTRY', () => {
- const registryIds = Object.keys(ITEM_REGISTRY);
- expect(registryIds).toContain('double-points');
- expect(registryIds).toContain('point-steal');
- expect(registryIds).toContain('swap-points');
- expect(registryIds).toContain('give-points');
- expect(registryIds).toContain('two-options');
- expect(registryIds).toContain('skip');
- expect(registryIds).toContain('gamble');
- });
- });
- describe('Edge Cases', () => {
- test('Should handle questions with pre-existing items', () => {
- const questionsWithItems = Array(5).fill(null).map((_, i) => ({
- text: `Question ${i}`,
- item: i === 0 ? doublePointsItem : null,
- }));
- const result = assignItemsToQuestions(
- questionsWithItems as GameQuestion[],
- 'some'
- );
- // The function resets all items to null first, then assigns new ones
- // So pre-existing items are cleared and may or may not get a new item
- const itemCount = result.filter(q => q.item !== null).length;
- expect(itemCount).toBeGreaterThanOrEqual(2); // "some" density should have at least 2 items
- });
- test('Should handle more items requested than questions available', () => {
- const fewQuestions = Array(2).fill(null).map((_, i) => ({
- text: `Question ${i}`,
- item: null,
- }));
- const result = assignItemsToQuestions(
- fewQuestions as GameQuestion[],
- 'a-lot' // Would request 3+ items but only 2 questions
- );
- const itemCount = result.filter(q => q.item !== null).length;
- expect(itemCount).toBeLessThanOrEqual(2);
- });
- });
- </file>
- <file path="quizForm.test.tsx">
- import { describe, test, expect } from 'vitest';
- import type { MultipleChoiceQuestion } from '~/lib/types';
- // Mock quiz validation functions
- export const validateQuizQuestion = (question: Partial<MultipleChoiceQuestion>): string[] => {
- const errors: string[] = [];
- if (!question.text || question.text.trim().length === 0) {
- errors.push('Question text is required');
- }
- if (!question.correct_answer || question.correct_answer.trim().length === 0) {
- errors.push('Correct answer is required');
- }
- if (!question.options || question.options.length === 0) {
- errors.push('At least one option is required');
- } else {
- const invalidAnswers = question.options.filter(
- (answer: string) => !answer || answer.trim().length === 0
- );
- if (invalidAnswers.length > 0) {
- errors.push('All options must have text');
- }
- }
- // Check for duplicate answers
- if (question.correct_answer && question.options) {
- const allAnswers = question.options;
- const uniqueAnswers = new Set(allAnswers.map((a: string) => a.trim().toLowerCase()));
- if (uniqueAnswers.size < allAnswers.length) {
- errors.push('All options must be unique');
- }
- // Check that correct answer is among options
- if (!question.options.includes(question.correct_answer)) {
- errors.push('Correct answer must be one of the options');
- }
- }
- return errors;
- };
- export const formatQuizQuestion = (question: Partial<MultipleChoiceQuestion>): MultipleChoiceQuestion => {
- const formatted: any = {
- text: question.text?.trim() || '',
- correct_answer: question.correct_answer?.trim() || '',
- options: question.options?.map((a: string) => a.trim()).filter((a: string) => a.length > 0) || [],
- };
- if (question.explanation?.trim()) {
- formatted.explanation = question.explanation.trim();
- }
- return formatted;
- };
- export const shuffleAnswers = (question: MultipleChoiceQuestion): string[] => {
- const allAnswers = [...question.options];
- // Fisher-Yates shuffle
- const shuffled = [...allAnswers];
- for (let i = shuffled.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [shuffled[i], shuffled[j]] = [shuffled[j]!, shuffled[i]!];
- }
- return shuffled;
- };
- describe('Quiz Form Validation', () => {
- describe('validateQuizQuestion', () => {
- test('Should validate required question text', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- correct_answer: 'Answer',
- options: ['Answer', 'Wrong'],
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toContain('Question text is required');
- });
- test('Should validate empty question text', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: ' ',
- correct_answer: 'Answer',
- options: ['Answer', 'Wrong'],
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toContain('Question text is required');
- });
- test('Should validate required correct answer', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'Question?',
- options: ['Wrong'],
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toContain('Correct answer is required');
- });
- test('Should validate required options', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'Question?',
- correct_answer: 'Answer',
- options: [],
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toContain('At least one option is required');
- });
- test('Should validate empty options', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'Question?',
- correct_answer: 'Answer',
- options: ['', 'Valid', ' '],
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toContain('All options must have text');
- });
- test('Should detect duplicate options', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'Question?',
- correct_answer: 'Answer',
- options: ['Answer', 'Answer', 'Other'],
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toContain('All options must be unique');
- });
- test('Should detect duplicate options case-insensitive', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'Question?',
- correct_answer: 'ANSWER',
- options: ['ANSWER', 'answer', 'Other'],
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toContain('All options must be unique');
- });
- test('Should pass validation for valid question', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'What is 2 + 2?',
- correct_answer: '4',
- options: ['3', '4', '5', '6'],
- explanation: 'Basic math',
- };
- const errors = validateQuizQuestion(question);
- expect(errors).toHaveLength(0);
- });
- test('Should handle multiple errors', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: '',
- correct_answer: '',
- options: [],
- };
- const errors = validateQuizQuestion(question);
- expect(errors.length).toBeGreaterThan(1);
- expect(errors).toContain('Question text is required');
- expect(errors).toContain('Correct answer is required');
- expect(errors).toContain('At least one option is required');
- });
- });
- describe('formatQuizQuestion', () => {
- test('Should trim whitespace from all fields', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: ' Question? ',
- correct_answer: ' Answer ',
- options: [' Answer ', ' Wrong 1 ', ' Wrong 2 '],
- explanation: ' Explanation ',
- };
- const formatted = formatQuizQuestion(question);
- expect(formatted.text).toBe('Question?');
- expect(formatted.correct_answer).toBe('Answer');
- expect(formatted.options).toEqual(['Answer', 'Wrong 1', 'Wrong 2']);
- expect(formatted.explanation).toBe('Explanation');
- });
- test('Should filter empty options', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'Question?',
- correct_answer: 'Answer',
- options: ['Answer', 'Wrong 1', '', ' ', 'Wrong 2'],
- };
- const formatted = formatQuizQuestion(question);
- expect(formatted.options).toEqual(['Answer', 'Wrong 1', 'Wrong 2']);
- });
- test('Should handle missing fields with defaults', () => {
- const question: Partial<MultipleChoiceQuestion> = {};
- const formatted = formatQuizQuestion(question);
- expect(formatted.text).toBe('');
- expect(formatted.correct_answer).toBe('');
- expect(formatted.options).toEqual([]);
- expect(formatted.explanation).toBeUndefined();
- });
- test('Should preserve valid data', () => {
- const question: Partial<MultipleChoiceQuestion> = {
- text: 'Valid Question',
- correct_answer: 'Valid Answer',
- options: ['Valid Answer', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
- explanation: 'Valid Explanation',
- };
- const formatted = formatQuizQuestion(question);
- expect(formatted).toEqual({
- text: 'Valid Question',
- correct_answer: 'Valid Answer',
- options: ['Valid Answer', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
- explanation: 'Valid Explanation',
- });
- });
- });
- describe('shuffleAnswers', () => {
- test('Should contain all answers', () => {
- const question: MultipleChoiceQuestion = {
- text: 'Question',
- correct_answer: 'Correct',
- options: ['Correct', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
- };
- const shuffled = shuffleAnswers(question);
- expect(shuffled).toHaveLength(4);
- expect(shuffled).toContain('Correct');
- expect(shuffled).toContain('Wrong 1');
- expect(shuffled).toContain('Wrong 2');
- expect(shuffled).toContain('Wrong 3');
- });
- test('Should produce different orders (probabilistic)', () => {
- const question: MultipleChoiceQuestion = {
- text: 'Question',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D', 'E', 'F'],
- };
- const results = new Set<string>();
- // Run shuffle multiple times
- for (let i = 0; i < 20; i++) {
- const shuffled = shuffleAnswers(question);
- results.add(shuffled.join(','));
- }
- // Should have produced multiple different orderings
- expect(results.size).toBeGreaterThan(1);
- });
- test('Should handle question with two options', () => {
- const question: MultipleChoiceQuestion = {
- text: 'Question',
- correct_answer: 'Yes',
- options: ['Yes', 'No'],
- };
- const shuffled = shuffleAnswers(question);
- expect(shuffled).toHaveLength(2);
- expect(shuffled).toContain('Yes');
- expect(shuffled).toContain('No');
- });
- });
- });
- describe('Quiz Data Management', () => {
- describe('Quiz Creation', () => {
- test('Should create new quiz with default values', () => {
- const newQuiz = {
- id: 'quiz-1',
- questions: [],
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- };
- expect(newQuiz.questions).toHaveLength(0);
- expect(newQuiz.id).toBeDefined();
- expect(newQuiz.createdAt).toBeDefined();
- });
- test('Should add question to quiz', () => {
- const quiz = {
- id: 'quiz-1',
- questions: [] as MultipleChoiceQuestion[],
- };
- const question: MultipleChoiceQuestion = {
- text: 'New Question',
- correct_answer: 'Answer',
- options: ['Answer', 'Wrong'],
- };
- quiz.questions.push(question);
- expect(quiz.questions).toHaveLength(1);
- expect(quiz.questions[0]).toEqual(question);
- });
- test('Should remove question from quiz', () => {
- const quiz = {
- questions: [
- { text: 'Q1', correct_answer: 'A1', options: ['A1', 'W1'] },
- { text: 'Q2', correct_answer: 'A2', options: ['A2', 'W2'] },
- { text: 'Q3', correct_answer: 'A3', options: ['A3', 'W3'] },
- ] as MultipleChoiceQuestion[],
- };
- quiz.questions.splice(1, 1); // Remove Q2
- expect(quiz.questions).toHaveLength(2);
- expect(quiz.questions[0]!.text).toBe('Q1');
- expect(quiz.questions[1]!.text).toBe('Q3');
- });
- test('Should update question in quiz', () => {
- const quiz = {
- questions: [
- { text: 'Original', correct_answer: 'A', options: ['A', 'W'] },
- ] as MultipleChoiceQuestion[],
- };
- quiz.questions[0] = {
- text: 'Updated',
- correct_answer: 'B',
- options: ['B', 'X', 'Y'],
- };
- expect(quiz.questions[0]!.text).toBe('Updated');
- expect(quiz.questions[0]!.correct_answer).toBe('B');
- expect(quiz.questions[0]!.options).toEqual(['B', 'X', 'Y']);
- });
- });
- describe('Quiz Statistics', () => {
- test('Should calculate total questions', () => {
- const quiz = {
- questions: [
- { text: 'Q1', correct_answer: 'A', options: ['A', 'W'] },
- { text: 'Q2', correct_answer: 'A', options: ['A', 'W'] },
- { text: 'Q3', correct_answer: 'A', options: ['A', 'W'] },
- ],
- };
- expect(quiz.questions.length).toBe(3);
- });
- test('Should calculate average options per question', () => {
- const quiz = {
- questions: [
- { text: 'Q1', correct_answer: 'A', options: ['A', 'B', 'C', 'D'] },
- { text: 'Q2', correct_answer: 'A', options: ['A', 'B', 'C'] },
- { text: 'Q3', correct_answer: 'A', options: ['A', 'B', 'C', 'D', 'E'] },
- ] as MultipleChoiceQuestion[],
- };
- const totalOptions = quiz.questions.reduce(
- (sum, q) => sum + q.options.length,
- 0
- );
- const avgOptions = totalOptions / quiz.questions.length;
- expect(avgOptions).toBe(4); // (4 + 3 + 5) / 3 = 4
- });
- test('Should count questions with explanations', () => {
- const quiz = {
- questions: [
- { text: 'Q1', correct_answer: 'A', options: ['A', 'B'], explanation: 'E1' },
- { text: 'Q2', correct_answer: 'A', options: ['A', 'B'] },
- { text: 'Q3', correct_answer: 'A', options: ['A', 'B'], explanation: 'E3' },
- ] as MultipleChoiceQuestion[],
- };
- const withExplanations = quiz.questions.filter(q => q.explanation).length;
- expect(withExplanations).toBe(2);
- });
- });
- describe('Quiz Duplication', () => {
- test('Should deep clone quiz questions', () => {
- const original: MultipleChoiceQuestion[] = [
- { text: 'Q1', correct_answer: 'A', options: ['A', 'B', 'C'] },
- ];
- const cloned = JSON.parse(JSON.stringify(original)) as MultipleChoiceQuestion[];
- // Modify cloned
- cloned[0]!.text = 'Modified';
- cloned[0]!.options.push('D');
- // Original should be unchanged
- expect(original[0]!.text).toBe('Q1');
- expect(original[0]!.options).toHaveLength(3);
- });
- });
- });
- </file>
- <file path="quizIntegration.test.ts">
- import { describe, test, expect, beforeEach } from 'vitest';
- import { server } from '~/test-utils/mocks/server';
- import { http, HttpResponse } from 'msw';
- import type { MultipleChoiceQuestion } from '~/lib/types';
- describe('Quiz API Integration Tests with MSW', () => {
- beforeEach(() => {
- // Reset handlers to default state before each test
- server.resetHandlers();
- });
- describe('Quiz Generation Flow', () => {
- test('Should generate quiz with specific question count', async () => {
- let capturedRequest: Request | null = null;
- // Override handler to capture request details
- server.use(
- http.post('/generateQuiz', async ({ request }) => {
- capturedRequest = request;
- const formData = await request.formData();
- const questionCount = parseInt(formData.get('questionCount') as string) || 5;
- const questions: MultipleChoiceQuestion[] = Array.from(
- { length: questionCount },
- (_, i) => ({
- text: `Generated Question ${i + 1}`,
- correct_answer: `Answer ${i % 4 + 1}`,
- options: [`Answer 1`, `Answer 2`, `Answer 3`, `Answer 4`],
- explanation: `This is explanation ${i + 1}`,
- })
- );
- return HttpResponse.json({
- success: true,
- quiz: {
- id: 'test-quiz-id',
- multiple_choice: questions,
- created_at: new Date().toISOString(),
- },
- });
- })
- );
- // Simulate the API call
- const formData = new FormData();
- formData.append('lessonId', 'test-lesson');
- formData.append('questionCount', '10');
- formData.append('includeKnowledgeBase', 'true');
- const response = await fetch('/generateQuiz', {
- method: 'POST',
- body: formData,
- });
- const result = await response.json();
- // Assertions
- expect(result.success).toBe(true);
- expect(result.quiz.multiple_choice).toHaveLength(10);
- expect(result.quiz.id).toBe('test-quiz-id');
- expect(capturedRequest).not.toBeNull();
- });
- test('Should merge existing questions when generating new ones', async () => {
- const existingQuestions: MultipleChoiceQuestion[] = [
- {
- text: 'Existing Question 1',
- correct_answer: 'Existing Answer',
- options: ['Existing Answer', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
- },
- ];
- server.use(
- http.post('/generateQuiz', async ({ request }) => {
- const formData = await request.formData();
- const existingQuestionsStr = formData.get('existingQuestions') as string;
- const existing = existingQuestionsStr ? JSON.parse(existingQuestionsStr) : [];
- const questionCount = parseInt(formData.get('questionCount') as string) || 5;
- const newQuestions: MultipleChoiceQuestion[] = Array.from(
- { length: questionCount },
- (_, i) => ({
- text: `New Question ${i + 1}`,
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- })
- );
- return HttpResponse.json({
- success: true,
- quiz: {
- id: 'merged-quiz',
- multiple_choice: [...existing, ...newQuestions],
- },
- });
- })
- );
- const formData = new FormData();
- formData.append('existingQuestions', JSON.stringify(existingQuestions));
- formData.append('questionCount', '3');
- const response = await fetch('/generateQuiz', {
- method: 'POST',
- body: formData,
- });
- const result = await response.json();
- expect(result.success).toBe(true);
- expect(result.quiz.multiple_choice).toHaveLength(4); // 1 existing + 3 new
- expect(result.quiz.multiple_choice[0].text).toBe('Existing Question 1');
- });
- });
- describe('Error Scenarios', () => {
- test('Should handle server errors gracefully', async () => {
- server.use(
- http.post('/generateQuiz', () => {
- return HttpResponse.json(
- { success: false, error: 'Internal server error' },
- { status: 500 }
- );
- })
- );
- const response = await fetch('/generateQuiz', {
- method: 'POST',
- body: new FormData(),
- });
- const result = await response.json();
- expect(response.status).toBe(500);
- expect(result.success).toBe(false);
- expect(result.error).toBe('Internal server error');
- });
- test('Should handle network timeouts', async () => {
- server.use(
- http.post('/generateQuiz', async () => {
- // Simulate a timeout
- await new Promise(resolve => setTimeout(resolve, 1000));
- return HttpResponse.json({ success: true });
- })
- );
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 100);
- try {
- await fetch('/generateQuiz', {
- method: 'POST',
- body: new FormData(),
- signal: controller.signal,
- });
- clearTimeout(timeoutId);
- expect.fail('Should have aborted');
- } catch (error: any) {
- clearTimeout(timeoutId);
- expect(error.name).toBe('AbortError');
- }
- });
- test('Should handle malformed responses', async () => {
- server.use(
- http.post('/generateQuiz', () => {
- return new HttpResponse('Invalid JSON', {
- status: 200,
- headers: { 'Content-Type': 'application/json' },
- });
- })
- );
- const response = await fetch('/generateQuiz', {
- method: 'POST',
- body: new FormData(),
- });
- try {
- await response.json();
- expect.fail('Should have thrown JSON parse error');
- } catch (error) {
- expect(error).toBeDefined();
- }
- });
- });
- describe('Concurrent Requests', () => {
- test('Should handle multiple concurrent quiz generations', async () => {
- let requestCount = 0;
- server.use(
- http.post('/generateQuiz', async () => {
- requestCount++;
- const requestId = requestCount;
- // Simulate varying processing times
- await new Promise(resolve => setTimeout(resolve, Math.random() * 100));
- return HttpResponse.json({
- success: true,
- quiz: {
- id: `quiz-${requestId}`,
- multiple_choice: [
- {
- text: `Question from request ${requestId}`,
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- },
- ],
- },
- });
- })
- );
- // Make multiple concurrent requests
- const requests = Array.from({ length: 5 }, (_, i) => {
- const formData = new FormData();
- formData.append('lessonId', `lesson-${i}`);
- return fetch('/generateQuiz', { method: 'POST', body: formData });
- });
- const responses = await Promise.all(requests);
- const results = await Promise.all(responses.map(r => r.json()));
- // All requests should succeed
- results.forEach((result) => {
- expect(result.success).toBe(true);
- expect(result.quiz.id).toMatch(/^quiz-\d+$/);
- });
- expect(requestCount).toBe(5);
- });
- });
- describe('Request Validation', () => {
- test('Should validate required fields', async () => {
- server.use(
- http.post('/generateQuiz', async ({ request }) => {
- const formData = await request.formData();
- const lessonId = formData.get('lessonId');
- if (!lessonId) {
- return HttpResponse.json(
- { success: false, error: 'Lesson ID is required' },
- { status: 400 }
- );
- }
- return HttpResponse.json({ success: true, quiz: { id: 'valid-quiz' } });
- })
- );
- // Request without lessonId
- const response1 = await fetch('/generateQuiz', {
- method: 'POST',
- body: new FormData(),
- });
- const result1 = await response1.json();
- expect(result1.success).toBe(false);
- expect(result1.error).toBe('Lesson ID is required');
- // Request with lessonId
- const formData = new FormData();
- formData.append('lessonId', 'test-lesson');
- const response2 = await fetch('/generateQuiz', {
- method: 'POST',
- body: formData,
- });
- const result2 = await response2.json();
- expect(result2.success).toBe(true);
- });
- });
- });
- </file>
- <file path="teamTriviaReducer.test.ts">
- import { describe, test, expect, beforeEach } from 'vitest';
- import { teamTriviaReducer, initialState, BASE_POINTS } from '../TeamTrivia/context/teamTriviaReducer';
- import type { TeamTriviaContextState } from '../TeamTrivia/context/teamTriviaContext';
- import type { GameQuestion } from '../TeamTrivia/lib/types';
- import { doublePointsItem, pointStealItem, givePointsItem } from '../TeamTrivia/lib/itemRegistry';
- describe('teamTriviaReducer - Core Game Logic', () => {
- let state: TeamTriviaContextState;
- beforeEach(() => {
- state = { ...initialState };
- });
- describe('Game Setup Actions', () => {
- test('SET_NUM_TEAMS should update number of teams', () => {
- const newState = teamTriviaReducer(state, {
- type: 'SET_NUM_TEAMS',
- payload: 4,
- });
- expect(newState.numTeams).toBe(4);
- });
- test('SET_NUM_QUESTIONS should update number of questions', () => {
- const newState = teamTriviaReducer(state, {
- type: 'SET_NUM_QUESTIONS',
- payload: 10,
- });
- expect(newState.selectedNumQuestions).toBe(10);
- });
- test('SET_SHOW_OPTIONS should toggle options visibility', () => {
- expect(state.showOptions).toBe(true);
- const newState = teamTriviaReducer(state, {
- type: 'SET_SHOW_OPTIONS',
- payload: false,
- });
- expect(newState.showOptions).toBe(false);
- });
- test('SET_SHOW_EXPLANATIONS should toggle explanations visibility', () => {
- expect(state.showExplanations).toBe(true);
- const newState = teamTriviaReducer(state, {
- type: 'SET_SHOW_EXPLANATIONS',
- payload: false,
- });
- expect(newState.showExplanations).toBe(false);
- });
- test('SET_ITEM_DENSITY should update item density', () => {
- expect(state.itemDensity).toBe('none');
- const newState = teamTriviaReducer(state, {
- type: 'SET_ITEM_DENSITY',
- payload: 'some',
- });
- expect(newState.itemDensity).toBe('some');
- });
- });
- describe('Game Initialization', () => {
- const mockQuestions = [
- {
- text: 'Question 1',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- explanation: 'Explanation 1',
- },
- {
- text: 'Question 2',
- correct_answer: 'B',
- options: ['A', 'B', 'C', 'D'],
- explanation: 'Explanation 2',
- },
- ];
- test('INITIALIZE_GAME should set up teams and questions', () => {
- state.numTeams = 3;
- state.selectedNumQuestions = 2;
- const newState = teamTriviaReducer(state, {
- type: 'INITIALIZE_GAME',
- payload: {
- questions: mockQuestions,
- itemDensity: 'none',
- },
- });
- expect(newState.gameState).toBe('playing');
- expect(newState.teams).toHaveLength(3);
- expect(newState.gameQuestions).toHaveLength(2);
- expect(newState.currentTeamIndex).toBe(0);
- expect(newState.isEndingEarly).toBe(false);
- // Check team initialization
- newState.teams.forEach((team, index) => {
- expect(team.id).toBe(index);
- expect(team.name).toBe(`Team ${index + 1}`);
- expect(team.score).toBe(0);
- expect(team.color).toBeDefined();
- });
- // Check question initialization
- newState.gameQuestions.forEach((question) => {
- expect(question.revealed).toBe(false);
- expect(question.answerResult).toBe(null);
- expect(question.item).toBe(null);
- });
- });
- test('INITIALIZE_GAME should assign items based on density', () => {
- state.numTeams = 2;
- state.selectedNumQuestions = 2;
- const newState = teamTriviaReducer(state, {
- type: 'INITIALIZE_GAME',
- payload: {
- questions: mockQuestions,
- itemDensity: 'a-lot',
- },
- });
- // With "a-lot" density and 2 questions, at least one should have an item
- const hasItems = newState.gameQuestions.some((q) => q.item !== null);
- expect(hasItems).toBe(true);
- });
- });
- describe('Question Selection and Answering', () => {
- beforeEach(() => {
- // Set up a basic game state
- state = {
- ...state,
- gameState: 'playing',
- teams: [
- { id: 0, name: 'Team 1', score: 0, color: 'blue' },
- { id: 1, name: 'Team 2', score: 0, color: 'red' },
- ],
- gameQuestions: [
- {
- text: 'Question 1',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- revealed: false,
- answerResult: null,
- item: null,
- explanation: 'Explanation',
- } as GameQuestion,
- ],
- currentTeamIndex: 0,
- };
- });
- test('SELECT_QUESTION should set current question', () => {
- const newState = teamTriviaReducer(state, {
- type: 'SELECT_QUESTION',
- payload: 0,
- });
- expect(newState.currentQuestion).toBe(state.gameQuestions[0]);
- expect(newState.gameState).toBe('question_view');
- expect(newState.selectedAnswer).toBe(null);
- expect(newState.isAnswerCorrect).toBe(null);
- });
- test('SELECT_QUESTION should handle questions with items', () => {
- state.gameQuestions[0]!.item = doublePointsItem;
- const newState = teamTriviaReducer(state, {
- type: 'SELECT_QUESTION',
- payload: 0,
- });
- expect(newState.currentItemForReveal).toBe(doublePointsItem);
- expect(newState.gameState).toBe('item_view');
- expect(newState.showItemAfterAnswer).toBe(true);
- });
- test('SELECT_ANSWER should update selected answer', () => {
- state.gameState = 'question_view';
- const newState = teamTriviaReducer(state, {
- type: 'SELECT_ANSWER',
- payload: 'A',
- });
- expect(newState.selectedAnswer).toBe('A');
- });
- test('CHECK_ANSWER should award points for correct answer', () => {
- state.gameState = 'question_view';
- state.currentQuestion = state.gameQuestions[0]!;
- state.selectedAnswer = 'A';
- const newState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(newState.isAnswerCorrect).toBe(true);
- expect(newState.teams[0]!.score).toBe(BASE_POINTS);
- expect(newState.gameState).toBe('answer_view');
- expect(newState.gameQuestions[0]!.revealed).toBe(true);
- expect(newState.gameQuestions[0]!.answerResult).toBe('correct');
- });
- test('CHECK_ANSWER should not award points for incorrect answer', () => {
- state.gameState = 'question_view';
- state.currentQuestion = state.gameQuestions[0]!;
- state.selectedAnswer = 'B';
- const newState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(newState.isAnswerCorrect).toBe(false);
- expect(newState.teams[0]!.score).toBe(0);
- expect(newState.gameState).toBe('answer_view');
- expect(newState.gameQuestions[0]!.answerResult).toBe('incorrect');
- });
- });
- describe('Power-Up Effects - Double Points', () => {
- beforeEach(() => {
- state = {
- ...state,
- gameState: 'question_view',
- teams: [
- { id: 0, name: 'Team 1', score: 0, color: 'blue' },
- ],
- currentTeamIndex: 0,
- currentQuestion: {
- text: 'Question',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- revealed: false,
- answerResult: null,
- item: doublePointsItem,
- explanation: 'Explanation',
- } as GameQuestion,
- selectedAnswer: 'A',
- showItemAfterAnswer: true,
- gameQuestions: [],
- };
- });
- test('Double Points should award 2x points when correct', () => {
- const newState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(newState.isAnswerCorrect).toBe(true);
- expect(newState.teams[0]!.score).toBe(BASE_POINTS * 2);
- expect(newState.gameState).toBe('answer_view');
- });
- test('Double Points should award no points when incorrect', () => {
- state.selectedAnswer = 'B';
- const newState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(newState.isAnswerCorrect).toBe(false);
- expect(newState.teams[0]!.score).toBe(0);
- expect(newState.gameState).toBe('answer_view');
- });
- });
- describe('Power-Up Effects - Point Steal', () => {
- beforeEach(() => {
- state = {
- ...state,
- gameState: 'question_view',
- teams: [
- { id: 0, name: 'Team 1', score: 0, color: 'blue' },
- { id: 1, name: 'Team 2', score: 20, color: 'red' },
- ],
- currentTeamIndex: 0,
- currentQuestion: {
- text: 'Question',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- revealed: false,
- answerResult: null,
- item: pointStealItem,
- explanation: 'Explanation',
- } as GameQuestion,
- selectedAnswer: 'A',
- showItemAfterAnswer: true,
- gameQuestions: [],
- };
- });
- test('Point Steal should trigger team selection when correct', () => {
- const newState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(newState.isAnswerCorrect).toBe(true);
- expect(newState.teams[0]!.score).toBe(BASE_POINTS); // Base points awarded
- expect(newState.gameState).toBe('team_selection');
- expect(newState.stealPointsAmount).toBe(5);
- });
- test('Point Steal should transfer points between teams', () => {
- state.stealPointsAmount = 5;
- const newState = teamTriviaReducer(state, {
- type: 'SELECT_TEAM_FOR_STEAL',
- payload: 1,
- });
- expect(newState.teams[0]!.score).toBe(5); // Current team gains 5
- expect(newState.teams[1]!.score).toBe(15); // Target team loses 5
- expect(newState.gameState).toBe('playing');
- });
- test('Point Steal should not allow stealing from self', () => {
- state.stealPointsAmount = 5;
- const newState = teamTriviaReducer(state, {
- type: 'SELECT_TEAM_FOR_STEAL',
- payload: 0, // Same as current team
- });
- // State should not change
- expect(newState).toEqual(state);
- });
- test('Point Steal should not reduce target team below 0', () => {
- state.teams[1]!.score = 3; // Less than steal amount
- state.stealPointsAmount = 5;
- const newState = teamTriviaReducer(state, {
- type: 'SELECT_TEAM_FOR_STEAL',
- payload: 1,
- });
- expect(newState.teams[0]!.score).toBe(5);
- expect(newState.teams[1]!.score).toBe(0); // Should be 0, not negative
- });
- });
- describe('Power-Up Effects - Swap Points', () => {
- test('SWAP_POINTS should exchange scores between teams', () => {
- state = {
- ...state,
- teams: [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- { id: 1, name: 'Team 2', score: 30, color: 'red' },
- ],
- };
- const newState = teamTriviaReducer(state, {
- type: 'SWAP_POINTS',
- payload: { teamIndex1: 0, teamIndex2: 1 },
- });
- expect(newState.teams[0]!.score).toBe(30);
- expect(newState.teams[1]!.score).toBe(10);
- });
- test('SWAP_POINTS should not swap with self', () => {
- state = {
- ...state,
- teams: [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- ],
- };
- const newState = teamTriviaReducer(state, {
- type: 'SWAP_POINTS',
- payload: { teamIndex1: 0, teamIndex2: 0 },
- });
- expect(newState).toEqual(state);
- });
- });
- describe('Power-Up Effects - Give Points', () => {
- beforeEach(() => {
- state = {
- ...state,
- gameState: 'question_view',
- teams: [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- { id: 1, name: 'Team 2', score: 20, color: 'red' },
- ],
- currentTeamIndex: 0,
- currentQuestion: {
- text: 'Question',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- revealed: false,
- answerResult: null,
- item: givePointsItem,
- explanation: 'Explanation',
- } as GameQuestion,
- selectedAnswer: 'A',
- showItemAfterAnswer: true,
- gameQuestions: [],
- };
- });
- test('Give Points should trigger team selection regardless of answer', () => {
- const correctState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(correctState.isAnswerCorrect).toBe(true);
- expect(correctState.teams[0]!.score).toBe(20); // Base points awarded
- expect(correctState.gameState).toBe('team_selection');
- // Test with incorrect answer
- state.selectedAnswer = 'B';
- const incorrectState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(incorrectState.isAnswerCorrect).toBe(false);
- expect(incorrectState.teams[0]!.score).toBe(10); // No base points
- expect(incorrectState.gameState).toBe('team_selection');
- });
- });
- describe('Manual Scoring', () => {
- beforeEach(() => {
- state = {
- ...state,
- gameState: 'answer_view',
- teams: [
- { id: 0, name: 'Team 1', score: 0, color: 'blue' },
- ],
- currentTeamIndex: 0,
- currentQuestion: {
- text: 'Question',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- revealed: false,
- answerResult: null,
- item: null,
- explanation: 'Explanation',
- } as GameQuestion,
- gameQuestions: [],
- };
- });
- test('MANUAL_SCORE should award points when marked correct', () => {
- const newState = teamTriviaReducer(state, {
- type: 'MANUAL_SCORE',
- payload: true,
- });
- expect(newState.isAnswerCorrect).toBe(true);
- expect(newState.teams[0]!.score).toBe(BASE_POINTS);
- });
- test('MANUAL_SCORE should not award points when marked incorrect', () => {
- const newState = teamTriviaReducer(state, {
- type: 'MANUAL_SCORE',
- payload: false,
- });
- expect(newState.isAnswerCorrect).toBe(false);
- expect(newState.teams[0]!.score).toBe(0);
- });
- });
- describe('Game Progression', () => {
- beforeEach(() => {
- state = {
- ...state,
- gameState: 'answer_view',
- teams: [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- { id: 1, name: 'Team 2', score: 20, color: 'red' },
- ],
- currentTeamIndex: 0,
- gameQuestions: [
- { revealed: true, item: null } as GameQuestion,
- { revealed: false, item: null } as GameQuestion,
- ],
- };
- });
- test('NEXT_TURN should advance to next team', () => {
- const newState = teamTriviaReducer(state, {
- type: 'NEXT_TURN',
- });
- expect(newState.currentTeamIndex).toBe(1);
- expect(newState.gameState).toBe('playing');
- expect(newState.currentQuestion).toBe(null);
- expect(newState.selectedAnswer).toBe(null);
- });
- test('NEXT_TURN should wrap around to first team after last', () => {
- state.currentTeamIndex = 1;
- const newState = teamTriviaReducer(state, {
- type: 'NEXT_TURN',
- });
- expect(newState.currentTeamIndex).toBe(0);
- expect(newState.gameState).toBe('playing');
- });
- test('NEXT_TURN should end game when all questions revealed', () => {
- state.gameQuestions[1]!.revealed = true;
- const newState = teamTriviaReducer(state, {
- type: 'NEXT_TURN',
- });
- expect(newState.gameState).toBe('game_over');
- });
- test('END_GAME_EARLY should mark for ending after round', () => {
- state.gameState = 'playing';
- const newState = teamTriviaReducer(state, {
- type: 'END_GAME_EARLY',
- });
- expect(newState.isEndingEarly).toBe(true);
- });
- test('RESET_GAME should retain settings but reset game state', () => {
- state.numTeams = 4;
- state.selectedNumQuestions = 15;
- state.showOptions = false;
- state.itemDensity = 'some';
- const newState = teamTriviaReducer(state, {
- type: 'RESET_GAME',
- });
- expect(newState.numTeams).toBe(4);
- expect(newState.selectedNumQuestions).toBe(15);
- expect(newState.showOptions).toBe(false);
- expect(newState.itemDensity).toBe('some');
- expect(newState.gameState).toBe('setup');
- expect(newState.teams).toEqual([]);
- expect(newState.gameQuestions).toEqual([]);
- });
- });
- describe('Direct Point Manipulation', () => {
- beforeEach(() => {
- state = {
- ...state,
- teams: [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- { id: 1, name: 'Team 2', score: 20, color: 'red' },
- ],
- };
- });
- test('ADD_POINTS should increase team score', () => {
- const newState = teamTriviaReducer(state, {
- type: 'ADD_POINTS',
- payload: { teamIndex: 0, points: 5 },
- });
- expect(newState.teams[0]!.score).toBe(15);
- });
- test('DEDUCT_POINTS should decrease team score', () => {
- const newState = teamTriviaReducer(state, {
- type: 'DEDUCT_POINTS',
- payload: { teamIndex: 1, points: 5 },
- });
- expect(newState.teams[1]!.score).toBe(15);
- });
- test('DEDUCT_POINTS should not go below zero', () => {
- const newState = teamTriviaReducer(state, {
- type: 'DEDUCT_POINTS',
- payload: { teamIndex: 0, points: 15 },
- });
- expect(newState.teams[0]!.score).toBe(0);
- });
- test('STEAL_POINTS should transfer points correctly', () => {
- const newState = teamTriviaReducer(state, {
- type: 'STEAL_POINTS',
- payload: { fromTeamIndex: 1, toTeamIndex: 0, points: 5 },
- });
- expect(newState.teams[0]!.score).toBe(15);
- expect(newState.teams[1]!.score).toBe(15);
- });
- test('STEAL_POINTS should handle insufficient points', () => {
- const newState = teamTriviaReducer(state, {
- type: 'STEAL_POINTS',
- payload: { fromTeamIndex: 0, toTeamIndex: 1, points: 15 },
- });
- expect(newState.teams[0]!.score).toBe(0);
- expect(newState.teams[1]!.score).toBe(30);
- });
- });
- describe('Edge Cases and Error Handling', () => {
- test('Should handle invalid team indices gracefully', () => {
- state.teams = [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- ];
- let newState = teamTriviaReducer(state, {
- type: 'ADD_POINTS',
- payload: { teamIndex: 5, points: 10 },
- });
- expect(newState).toEqual(state);
- newState = teamTriviaReducer(state, {
- type: 'DEDUCT_POINTS',
- payload: { teamIndex: -1, points: 5 },
- });
- expect(newState).toEqual(state);
- });
- test('Should handle selecting already revealed questions', () => {
- state.gameQuestions = [
- { revealed: true, item: null } as GameQuestion,
- ];
- const newState = teamTriviaReducer(state, {
- type: 'SELECT_QUESTION',
- payload: 0,
- });
- expect(newState).toEqual(state);
- });
- test('Should handle CHECK_ANSWER with no current question', () => {
- state.gameState = 'question_view';
- state.currentQuestion = null;
- const newState = teamTriviaReducer(state, {
- type: 'CHECK_ANSWER',
- });
- expect(newState).toEqual(state);
- });
- test('Should handle CONTINUE_AFTER_ITEM action', () => {
- state.gameState = 'item_view';
- const newState = teamTriviaReducer(state, {
- type: 'CONTINUE_AFTER_ITEM',
- });
- expect(newState.gameState).toBe('question_view');
- });
- test('Should handle SET_CURRENT_ITEM action', () => {
- const newState = teamTriviaReducer(state, {
- type: 'SET_CURRENT_ITEM',
- payload: doublePointsItem,
- });
- expect(newState.currentItemForReveal).toBe(doublePointsItem);
- });
- });
- describe('Complex Scenarios', () => {
- test('Should handle complete game flow with items', () => {
- // Initialize game
- state.numTeams = 2;
- state.selectedNumQuestions = 2;
- let currentState = teamTriviaReducer(state, {
- type: 'INITIALIZE_GAME',
- payload: {
- questions: [
- {
- text: 'Q1',
- correct_answer: 'A',
- options: ['A', 'B', 'C', 'D'],
- explanation: 'E1',
- },
- {
- text: 'Q2',
- correct_answer: 'B',
- options: ['A', 'B', 'C', 'D'],
- explanation: 'E2',
- },
- ],
- itemDensity: 'a-lot', // Use 'a-lot' to guarantee items are assigned
- },
- });
- expect(currentState.gameState).toBe('playing');
- // Verify that an item was assigned to at least one question
- const hasItem = currentState.gameQuestions.some(q => q.item !== null);
- expect(hasItem).toBe(true);
- // If first question has an item, test the item flow
- const firstQuestionHasItem = currentState.gameQuestions[0]!.item !== null;
- // Team 1 selects question
- currentState = teamTriviaReducer(currentState, {
- type: 'SELECT_QUESTION',
- payload: 0,
- });
- if (firstQuestionHasItem) {
- expect(currentState.gameState).toBe('item_view');
- } else {
- expect(currentState.gameState).toBe('question_view');
- }
- // Continue after item if there was one
- if (firstQuestionHasItem) {
- currentState = teamTriviaReducer(currentState, {
- type: 'CONTINUE_AFTER_ITEM',
- });
- expect(currentState.gameState).toBe('question_view');
- // Verify that showItemAfterAnswer is set correctly based on item category
- if (currentState.currentQuestion?.item) {
- const itemCategory = currentState.currentQuestion.item.category;
- const shouldShowAfter = itemCategory === 'yellow' || itemCategory === 'purple' || itemCategory === 'orange';
- expect(currentState.showItemAfterAnswer).toBe(shouldShowAfter);
- }
- }
- // Select the correct answer for this question
- const correctAnswer = currentState.currentQuestion!.correct_answer;
- currentState = teamTriviaReducer(currentState, {
- type: 'SELECT_ANSWER',
- payload: correctAnswer,
- });
- // Check answer - this should process any item effects if present
- currentState = teamTriviaReducer(currentState, {
- type: 'CHECK_ANSWER',
- });
- // Note: In this complex flow test, we're testing the overall flow
- // Specific item logic is tested separately in dedicated test cases
- // So we'll just verify the base behavior here
- expect(currentState.isAnswerCorrect).toBe(true);
- // Some items trigger team selection (purple/orange categories)
- // Others go directly to answer view
- const validStates = ['answer_view', 'team_selection'];
- expect(validStates).toContain(currentState.gameState);
- // If we're in team selection, complete that flow
- if (currentState.gameState === 'team_selection') {
- // For testing, just select team 1 (if current team is 0)
- const targetTeam = currentState.currentTeamIndex === 0 ? 1 : 0;
- currentState = teamTriviaReducer(currentState, {
- type: 'SELECT_TEAM_FOR_STEAL',
- payload: targetTeam,
- });
- // After team selection, we should be back in playing state
- expect(currentState.gameState).toBe('playing');
- } else if (currentState.gameState === 'answer_view') {
- // We're in answer view, ready for next turn
- expect(currentState.teams[0]!.score).toBeGreaterThan(0);
- }
- // For this test flow, always advance to next turn to test the complete game cycle
- // Note: In real game, after team selection the same team continues, but for testing we move on
- if (currentState.gameState === 'answer_view') {
- currentState = teamTriviaReducer(currentState, {
- type: 'NEXT_TURN',
- });
- expect(currentState.currentTeamIndex).toBe(1);
- expect(currentState.gameState).toBe('playing');
- } else {
- // We're already in playing state after team selection
- // The flow test ends here - team 0 completed their action
- expect(currentState.gameState).toBe('playing');
- expect(currentState.currentTeamIndex).toBe(0); // Still team 0's turn
- }
- });
- test('Should handle all questions revealed triggering game over', () => {
- state = {
- ...state,
- gameState: 'answer_view',
- teams: [
- { id: 0, name: 'Team 1', score: 10, color: 'blue' },
- ],
- currentTeamIndex: 0,
- gameQuestions: [
- { revealed: true, item: null } as GameQuestion,
- { revealed: true, item: null } as GameQuestion,
- ],
- };
- const newState = teamTriviaReducer(state, {
- type: 'NEXT_TURN',
- });
- expect(newState.gameState).toBe('game_over');
- });
- });
- });
- </file>
- </files>
Advertisement
Add Comment
Please, Sign In to add comment