Guest User

Tests

a guest
Aug 27th, 2025
112
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. This file is a merged representation of the entire codebase, combined into a single document by Repomix.
  2.  
  3. <file_summary>
  4. This section contains a summary of this file.
  5.  
  6. <purpose>
  7. This file contains a packed representation of the entire repository's contents.
  8. It is designed to be easily consumable by AI systems for analysis, code review,
  9. or other automated processes.
  10. </purpose>
  11.  
  12. <file_format>
  13. The content is organized as follows:
  14. 1. This summary section
  15. 2. Repository information
  16. 3. Directory structure
  17. 4. Repository files (if enabled)
  18. 5. Multiple file entries, each consisting of:
  19.   - File path as an attribute
  20.   - Full contents of the file
  21. </file_format>
  22.  
  23. <usage_guidelines>
  24. - This file should be treated as read-only. Any changes should be made to the
  25.   original repository files, not this packed version.
  26. - When processing this file, use the file path to distinguish
  27.   between different files in the repository.
  28. - Be aware that this file may contain sensitive information. Handle it with
  29.   the same level of security as you would the original repository.
  30. </usage_guidelines>
  31.  
  32. <notes>
  33. - Some files may have been excluded based on .gitignore rules and Repomix's configuration
  34. - 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
  35. - Files matching patterns in .gitignore are excluded
  36. - Files matching default ignore patterns are excluded
  37. - Files are sorted by Git change count (files with more changes are at the bottom)
  38. </notes>
  39.  
  40. </file_summary>
  41.  
  42. <directory_structure>
  43. GameContext.test.tsx
  44. gamesModalHooks.test.tsx
  45. gameUtils.test.ts
  46. itemRegistry.test.ts
  47. quizForm.test.tsx
  48. quizIntegration.test.ts
  49. teamTriviaReducer.test.ts
  50. </directory_structure>
  51.  
  52. <files>
  53. This section contains the contents of the repository's files.
  54.  
  55. <file path="GameContext.test.tsx">
  56. import { describe, test, expect } from 'vitest';
  57. import { renderHook, act } from '@testing-library/react';
  58. import { ReactNode } from 'react';
  59. import { GameProvider, useGameContext, validateGameState } from '../GameContext';
  60.  
  61. describe('GameContext', () => {
  62.   const wrapper = ({ children }: { children: ReactNode }) => (
  63.     <GameProvider>{children}</GameProvider>
  64.   );
  65.  
  66.   describe('Initial State', () => {
  67.     test('Should initialize with default values', () => {
  68.       const { result } = renderHook(() => useGameContext(), { wrapper });
  69.  
  70.       expect(result.current.activeGame).toBe(null);
  71.       expect(result.current.numberOfTeams).toBe(0);
  72.       expect(result.current.numberOfQuestions).toBe(0);
  73.       expect(result.current.scores).toEqual({});
  74.       expect(result.current.endGameAfterRound).toBe(false);
  75.       expect(result.current.fontSizeMultiplier).toBe(1.7);
  76.       expect(result.current.teams).toEqual([]);
  77.     });
  78.   });
  79.  
  80.   describe('State Updates', () => {
  81.     test('Should update active game', () => {
  82.       const { result } = renderHook(() => useGameContext(), { wrapper });
  83.  
  84.       act(() => {
  85.         result.current.setActiveGame('team-trivia');
  86.       });
  87.  
  88.       expect(result.current.activeGame).toBe('team-trivia');
  89.  
  90.       act(() => {
  91.         result.current.setActiveGame(null);
  92.       });
  93.  
  94.       expect(result.current.activeGame).toBe(null);
  95.     });
  96.  
  97.     test('Should update number of teams', () => {
  98.       const { result } = renderHook(() => useGameContext(), { wrapper });
  99.  
  100.       act(() => {
  101.         result.current.setNumberOfTeams(4);
  102.       });
  103.  
  104.       expect(result.current.numberOfTeams).toBe(4);
  105.     });
  106.  
  107.     test('Should update number of questions', () => {
  108.       const { result } = renderHook(() => useGameContext(), { wrapper });
  109.  
  110.       act(() => {
  111.         result.current.setNumberOfQuestions(10);
  112.       });
  113.  
  114.       expect(result.current.numberOfQuestions).toBe(10);
  115.     });
  116.  
  117.     test('Should update scores', () => {
  118.       const { result } = renderHook(() => useGameContext(), { wrapper });
  119.  
  120.       const newScores = {
  121.         team1: 10,
  122.         team2: 20,
  123.       };
  124.  
  125.       act(() => {
  126.         result.current.setScores(newScores);
  127.       });
  128.  
  129.       expect(result.current.scores).toEqual(newScores);
  130.     });
  131.  
  132.     test('Should update endGameAfterRound', () => {
  133.       const { result } = renderHook(() => useGameContext(), { wrapper });
  134.  
  135.       act(() => {
  136.         result.current.setEndGameAfterRound(true);
  137.       });
  138.  
  139.       expect(result.current.endGameAfterRound).toBe(true);
  140.     });
  141.  
  142.     test('Should update font size multiplier', () => {
  143.       const { result } = renderHook(() => useGameContext(), { wrapper });
  144.  
  145.       act(() => {
  146.         result.current.setFontSizeMultiplier(2.5);
  147.       });
  148.  
  149.       expect(result.current.fontSizeMultiplier).toBe(2.5);
  150.     });
  151.  
  152.     test('Should update teams', () => {
  153.       const { result } = renderHook(() => useGameContext(), { wrapper });
  154.  
  155.       const newTeams = [
  156.         { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  157.         { id: 1, name: 'Team 2', score: 20, color: 'red' },
  158.       ];
  159.  
  160.       act(() => {
  161.         result.current.setTeams(newTeams);
  162.       });
  163.  
  164.       expect(result.current.teams).toEqual(newTeams);
  165.     });
  166.   });
  167.  
  168.   describe('Multiple Updates', () => {
  169.     test('Should handle multiple state updates', () => {
  170.       const { result } = renderHook(() => useGameContext(), { wrapper });
  171.  
  172.       act(() => {
  173.         result.current.setActiveGame('team-trivia');
  174.         result.current.setNumberOfTeams(3);
  175.         result.current.setNumberOfQuestions(15);
  176.         result.current.setFontSizeMultiplier(2.0);
  177.       });
  178.  
  179.       expect(result.current.activeGame).toBe('team-trivia');
  180.       expect(result.current.numberOfTeams).toBe(3);
  181.       expect(result.current.numberOfQuestions).toBe(15);
  182.       expect(result.current.fontSizeMultiplier).toBe(2.0);
  183.     });
  184.  
  185.     test('Should preserve other state when updating one value', () => {
  186.       const { result } = renderHook(() => useGameContext(), { wrapper });
  187.  
  188.       act(() => {
  189.         result.current.setNumberOfTeams(4);
  190.         result.current.setNumberOfQuestions(10);
  191.       });
  192.  
  193.       act(() => {
  194.         result.current.setActiveGame('team-trivia');
  195.       });
  196.  
  197.       expect(result.current.numberOfTeams).toBe(4);
  198.       expect(result.current.numberOfQuestions).toBe(10);
  199.       expect(result.current.activeGame).toBe('team-trivia');
  200.     });
  201.   });
  202.  
  203.   describe('Teams and Scores Integration', () => {
  204.     test('Should handle team and score updates together', () => {
  205.       const { result } = renderHook(() => useGameContext(), { wrapper });
  206.  
  207.       const teams = [
  208.         { id: 0, name: 'Team A', score: 0, color: 'blue' },
  209.         { id: 1, name: 'Team B', score: 0, color: 'red' },
  210.       ];
  211.  
  212.       act(() => {
  213.         result.current.setTeams(teams);
  214.       });
  215.  
  216.       const scores = {
  217.         'Team A': 15,
  218.         'Team B': 25,
  219.       };
  220.  
  221.       act(() => {
  222.         result.current.setScores(scores);
  223.       });
  224.  
  225.       expect(result.current.teams).toEqual(teams);
  226.       expect(result.current.scores).toEqual(scores);
  227.     });
  228.   });
  229.  
  230.   describe('Error Handling', () => {
  231.     test('Should throw error when useGameContext is used outside provider', () => {
  232.       // Suppress console.error for this test
  233.       const originalError = console.error;
  234.       console.error = () => {};
  235.  
  236.       expect(() => {
  237.         renderHook(() => useGameContext());
  238.       }).toThrow('useGameContext must be used within a GameProvider');
  239.  
  240.       console.error = originalError;
  241.     });
  242.   });
  243. });
  244.  
  245. describe('validateGameState', () => {
  246.   test('Should validate number of teams', () => {
  247.     const result1 = validateGameState({ numberOfTeams: 2 });
  248.     expect(result1.isValid).toBe(true);
  249.     expect(result1.errors).toHaveLength(0);
  250.  
  251.     const result2 = validateGameState({ numberOfTeams: 1 });
  252.     expect(result2.isValid).toBe(false);
  253.     expect(result2.errors).toContain('Number of teams must be at least 2');
  254.  
  255.     const result3 = validateGameState({ numberOfTeams: 0 });
  256.     expect(result3.isValid).toBe(false);
  257.     expect(result3.errors).toContain('Number of teams must be at least 2');
  258.   });
  259.  
  260.   test('Should validate number of questions', () => {
  261.     const result1 = validateGameState({ numberOfQuestions: 1 });
  262.     expect(result1.isValid).toBe(true);
  263.     expect(result1.errors).toHaveLength(0);
  264.  
  265.     const result2 = validateGameState({ numberOfQuestions: 0 });
  266.     expect(result2.isValid).toBe(false);
  267.     expect(result2.errors).toContain('Number of questions must be at least 1');
  268.  
  269.     const result3 = validateGameState({ numberOfQuestions: -5 });
  270.     expect(result3.isValid).toBe(false);
  271.     expect(result3.errors).toContain('Number of questions must be at least 1');
  272.   });
  273.  
  274.   test('Should validate font size multiplier', () => {
  275.     const result1 = validateGameState({ fontSizeMultiplier: 1.5 });
  276.     expect(result1.isValid).toBe(true);
  277.     expect(result1.errors).toHaveLength(0);
  278.  
  279.     const result2 = validateGameState({ fontSizeMultiplier: 0.3 });
  280.     expect(result2.isValid).toBe(false);
  281.     expect(result2.errors).toContain('Font size multiplier must be between 0.5 and 5');
  282.  
  283.     const result3 = validateGameState({ fontSizeMultiplier: 6 });
  284.     expect(result3.isValid).toBe(false);
  285.     expect(result3.errors).toContain('Font size multiplier must be between 0.5 and 5');
  286.   });
  287.  
  288.   test('Should validate multiple fields', () => {
  289.     const result = validateGameState({
  290.       numberOfTeams: 1,
  291.       numberOfQuestions: 0,
  292.       fontSizeMultiplier: 10,
  293.     });
  294.  
  295.     expect(result.isValid).toBe(false);
  296.     expect(result.errors).toHaveLength(3);
  297.     expect(result.errors).toContain('Number of teams must be at least 2');
  298.     expect(result.errors).toContain('Number of questions must be at least 1');
  299.     expect(result.errors).toContain('Font size multiplier must be between 0.5 and 5');
  300.   });
  301.  
  302.   test('Should pass validation with all valid values', () => {
  303.     const result = validateGameState({
  304.       numberOfTeams: 4,
  305.       numberOfQuestions: 10,
  306.       fontSizeMultiplier: 2.0,
  307.     });
  308.  
  309.     expect(result.isValid).toBe(true);
  310.     expect(result.errors).toHaveLength(0);
  311.   });
  312.  
  313.   test('Should handle partial state validation', () => {
  314.     const result1 = validateGameState({ numberOfTeams: 3 });
  315.     expect(result1.isValid).toBe(true);
  316.  
  317.     const result2 = validateGameState({ fontSizeMultiplier: 1.8 });
  318.     expect(result2.isValid).toBe(true);
  319.   });
  320.  
  321.   test('Should handle empty state', () => {
  322.     const result = validateGameState({});
  323.     expect(result.isValid).toBe(true);
  324.     expect(result.errors).toHaveLength(0);
  325.   });
  326. });
  327. </file>
  328.  
  329. <file path="gamesModalHooks.test.tsx">
  330. import { describe, test, expect, vi, beforeEach } from 'vitest';
  331. import { renderHook, act } from '@testing-library/react';
  332. import { server } from '~/test-utils/mocks/server';
  333. import { errorHandlers } from '~/test-utils/mocks/handlers';
  334. import { useQuizFetchers } from '../GamesModal/hooks';
  335. import type { Quiz, ClientBox, TempImage } from '~/lib/types';
  336.  
  337. // Mock the React Router hooks
  338. vi.mock('react-router', () => ({
  339.   useFetcher: vi.fn(() => ({
  340.     state: 'idle',
  341.     data: null,
  342.     submit: vi.fn(),
  343.   })),
  344. }));
  345.  
  346. // Mock the stores
  347. vi.mock('~/stores/lessonStore', () => ({
  348.   useLessonStore: vi.fn(() => vi.fn()),
  349. }));
  350.  
  351. // Mock error context
  352. vi.mock('../../errorAndLog/errorAndLogContext', () => ({
  353.   useLogAndError: () => ({
  354.     setError: vi.fn(),
  355.     logEvent: vi.fn(),
  356.   }),
  357. }));
  358.  
  359. describe('GamesModal Hooks Integration Tests with MSW', () => {
  360.   const defaultProps = {
  361.     lessonId: 'test-lesson-123',
  362.     localQuiz: null,
  363.     editorContent: '',
  364.     hasChanges: false,
  365.     boxes: [] as ClientBox[],
  366.     tempImages: [] as TempImage[],
  367.     includeKnowledgeBase: false,
  368.     includeNotes: false,
  369.     includeCurrentContent: false,
  370.     questionCount: 5,
  371.     onQuizSave: vi.fn(),
  372.     onQuizDelete: vi.fn(),
  373.     setIsSubmitting: vi.fn(),
  374.     setIsDeleting: vi.fn(),
  375.     setIsSaving: vi.fn(),
  376.   };
  377.  
  378.   beforeEach(() => {
  379.     vi.clearAllMocks();
  380.     server.resetHandlers();
  381.   });
  382.  
  383.   describe('Quiz Generation with MSW', () => {
  384.     test('Should successfully generate quiz with MSW mock', async () => {
  385.       // The MSW handler will automatically intercept the request
  386.       // and return a mock quiz based on our handlers
  387.      
  388.       const { result } = renderHook(() =>
  389.         useQuizFetchers(
  390.           defaultProps.lessonId,
  391.           defaultProps.localQuiz,
  392.           'Test content for quiz generation',
  393.           defaultProps.hasChanges,
  394.           defaultProps.boxes,
  395.           defaultProps.tempImages,
  396.           true, // includeKnowledgeBase
  397.           false,
  398.           false,
  399.           10, // questionCount
  400.           defaultProps.onQuizSave,
  401.           defaultProps.onQuizDelete,
  402.           defaultProps.setIsSubmitting,
  403.           defaultProps.setIsDeleting,
  404.           defaultProps.setIsSaving
  405.         )
  406.       );
  407.  
  408.       // Since MSW will intercept the actual HTTP request,
  409.       // we can test that the handler returns the expected data structure
  410.       expect(result.current.handleGenerateQuiz).toBeDefined();
  411.     });
  412.  
  413.     test('Should handle quiz generation error with MSW', async () => {
  414.       // Override the default handler with an error handler
  415.       server.use(errorHandlers.quizGenerationError);
  416.  
  417.       const { result } = renderHook(() =>
  418.         useQuizFetchers(
  419.           defaultProps.lessonId,
  420.           defaultProps.localQuiz,
  421.           'Test content',
  422.           defaultProps.hasChanges,
  423.           defaultProps.boxes,
  424.           defaultProps.tempImages,
  425.           true,
  426.           false,
  427.           false,
  428.           5,
  429.           defaultProps.onQuizSave,
  430.           defaultProps.onQuizDelete,
  431.           defaultProps.setIsSubmitting,
  432.           defaultProps.setIsDeleting,
  433.           defaultProps.setIsSaving
  434.         )
  435.       );
  436.  
  437.       expect(result.current.handleGenerateQuiz).toBeDefined();
  438.     });
  439.  
  440.     test('Should handle network errors with MSW', async () => {
  441.       // Override with network error handler
  442.       server.use(errorHandlers.networkError);
  443.  
  444.       const { result } = renderHook(() =>
  445.         useQuizFetchers(
  446.           defaultProps.lessonId,
  447.           defaultProps.localQuiz,
  448.           'Test content',
  449.           defaultProps.hasChanges,
  450.           defaultProps.boxes,
  451.           defaultProps.tempImages,
  452.           false,
  453.           false,
  454.           true,
  455.           5,
  456.           defaultProps.onQuizSave,
  457.           defaultProps.onQuizDelete,
  458.           defaultProps.setIsSubmitting,
  459.           defaultProps.setIsDeleting,
  460.           defaultProps.setIsSaving
  461.         )
  462.       );
  463.  
  464.       expect(result.current.handleGenerateQuiz).toBeDefined();
  465.     });
  466.   });
  467.  
  468.   describe('Quiz Saving with MSW', () => {
  469.     test('Should successfully save quiz with MSW mock', async () => {
  470.       const existingQuiz: Quiz = {
  471.         id: 'existing-quiz',
  472.         multiple_choice: [
  473.           {
  474.             text: 'Question 1',
  475.             correct_answer: 'A',
  476.             options: ['A', 'B', 'C', 'D'],
  477.           },
  478.         ],
  479.         created_at: new Date().toISOString(),
  480.       };
  481.  
  482.       const { result } = renderHook(() =>
  483.         useQuizFetchers(
  484.           defaultProps.lessonId,
  485.           existingQuiz,
  486.           '',
  487.           true, // hasChanges
  488.           defaultProps.boxes,
  489.           defaultProps.tempImages,
  490.           false,
  491.           false,
  492.           false,
  493.           5,
  494.           defaultProps.onQuizSave,
  495.           defaultProps.onQuizDelete,
  496.           defaultProps.setIsSubmitting,
  497.           defaultProps.setIsDeleting,
  498.           defaultProps.setIsSaving
  499.         )
  500.       );
  501.  
  502.       expect(result.current.handleSaveChanges).toBeDefined();
  503.     });
  504.  
  505.     test('Should handle save error with MSW', async () => {
  506.       server.use(errorHandlers.quizSaveError);
  507.  
  508.       const existingQuiz: Quiz = {
  509.         id: 'existing-quiz',
  510.         multiple_choice: [],
  511.         created_at: new Date().toISOString(),
  512.       };
  513.  
  514.       const { result } = renderHook(() =>
  515.         useQuizFetchers(
  516.           defaultProps.lessonId,
  517.           existingQuiz,
  518.           '',
  519.           true,
  520.           defaultProps.boxes,
  521.           defaultProps.tempImages,
  522.           false,
  523.           false,
  524.           false,
  525.           5,
  526.           defaultProps.onQuizSave,
  527.           defaultProps.onQuizDelete,
  528.           defaultProps.setIsSubmitting,
  529.           defaultProps.setIsDeleting,
  530.           defaultProps.setIsSaving
  531.         )
  532.       );
  533.  
  534.       expect(result.current.handleSaveChanges).toBeDefined();
  535.     });
  536.   });
  537.  
  538.   describe('Quiz Deletion with MSW', () => {
  539.     test('Should successfully delete quiz with MSW mock', async () => {
  540.       const { result } = renderHook(() =>
  541.         useQuizFetchers(
  542.           defaultProps.lessonId,
  543.           defaultProps.localQuiz,
  544.           '',
  545.           false,
  546.           defaultProps.boxes,
  547.           defaultProps.tempImages,
  548.           false,
  549.           false,
  550.           false,
  551.           5,
  552.           defaultProps.onQuizSave,
  553.           defaultProps.onQuizDelete,
  554.           defaultProps.setIsSubmitting,
  555.           defaultProps.setIsDeleting,
  556.           defaultProps.setIsSaving
  557.         )
  558.       );
  559.  
  560.       expect(result.current.executeResetQuiz).toBeDefined();
  561.     });
  562.  
  563.     test('Should handle deletion error with MSW', async () => {
  564.       server.use(errorHandlers.quizDeleteError);
  565.  
  566.       const { result } = renderHook(() =>
  567.         useQuizFetchers(
  568.           defaultProps.lessonId,
  569.           defaultProps.localQuiz,
  570.           '',
  571.           false,
  572.           defaultProps.boxes,
  573.           defaultProps.tempImages,
  574.           false,
  575.           false,
  576.           false,
  577.           5,
  578.           defaultProps.onQuizSave,
  579.           defaultProps.onQuizDelete,
  580.           defaultProps.setIsSubmitting,
  581.           defaultProps.setIsDeleting,
  582.           defaultProps.setIsSaving
  583.         )
  584.       );
  585.  
  586.       expect(result.current.executeResetQuiz).toBeDefined();
  587.     });
  588.   });
  589.  
  590.   describe('Edge Cases with MSW', () => {
  591.     test('Should validate required parameters before API call', () => {
  592.       const { result } = renderHook(() =>
  593.         useQuizFetchers(
  594.           '', // Empty lessonId
  595.           null,
  596.           '',
  597.           false,
  598.           defaultProps.boxes,
  599.           defaultProps.tempImages,
  600.           false,
  601.           false,
  602.           false,
  603.           5,
  604.           defaultProps.onQuizSave,
  605.           defaultProps.onQuizDelete,
  606.           defaultProps.setIsSubmitting,
  607.           defaultProps.setIsDeleting,
  608.           defaultProps.setIsSaving
  609.         )
  610.       );
  611.  
  612.       act(() => {
  613.         result.current.handleGenerateQuiz();
  614.       });
  615.  
  616.       // Should not make API call with invalid parameters
  617.       expect(result.current.handleGenerateQuiz).toBeDefined();
  618.     });
  619.  
  620.     test('Should handle invalid question count', () => {
  621.       const { result } = renderHook(() =>
  622.         useQuizFetchers(
  623.           defaultProps.lessonId,
  624.           null,
  625.           'content',
  626.           false,
  627.           defaultProps.boxes,
  628.           defaultProps.tempImages,
  629.           false,
  630.           false,
  631.           false,
  632.           0, // Invalid question count
  633.           defaultProps.onQuizSave,
  634.           defaultProps.onQuizDelete,
  635.           defaultProps.setIsSubmitting,
  636.           defaultProps.setIsDeleting,
  637.           defaultProps.setIsSaving
  638.         )
  639.       );
  640.  
  641.       act(() => {
  642.         result.current.handleGenerateQuiz();
  643.       });
  644.  
  645.       expect(result.current.handleGenerateQuiz).toBeDefined();
  646.     });
  647.  
  648.     test('Should handle no content selected', () => {
  649.       const { result } = renderHook(() =>
  650.         useQuizFetchers(
  651.           defaultProps.lessonId,
  652.           null,
  653.           '', // No editor content
  654.           false,
  655.           [], // No boxes
  656.           [], // No images
  657.           false, // No knowledge base
  658.           false, // No notes
  659.           false, // No current content
  660.           5,
  661.           defaultProps.onQuizSave,
  662.           defaultProps.onQuizDelete,
  663.           defaultProps.setIsSubmitting,
  664.           defaultProps.setIsDeleting,
  665.           defaultProps.setIsSaving
  666.         )
  667.       );
  668.  
  669.       act(() => {
  670.         result.current.handleGenerateQuiz();
  671.       });
  672.  
  673.       expect(result.current.handleGenerateQuiz).toBeDefined();
  674.     });
  675.   });
  676. });
  677. </file>
  678.  
  679. <file path="gameUtils.test.ts">
  680. import { describe, test, expect } from 'vitest';
  681. import { shuffleArray, generateTeamColors } from '../TeamTrivia/lib/utils';
  682. import { TEAM_COLORS } from '../TeamTrivia/lib/constants';
  683.  
  684. describe('Game Utilities', () => {
  685.   describe('shuffleArray', () => {
  686.     test('Should return array of same length', () => {
  687.       const original = [1, 2, 3, 4, 5];
  688.       const shuffled = shuffleArray(original);
  689.      
  690.       expect(shuffled).toHaveLength(original.length);
  691.     });
  692.  
  693.     test('Should contain all original elements', () => {
  694.       const original = ['a', 'b', 'c', 'd'];
  695.       const shuffled = shuffleArray(original);
  696.      
  697.       original.forEach(item => {
  698.         expect(shuffled).toContain(item);
  699.       });
  700.     });
  701.  
  702.     test('Should not modify original array', () => {
  703.       const original = [1, 2, 3, 4, 5];
  704.       const originalCopy = [...original];
  705.       shuffleArray(original);
  706.      
  707.       expect(original).toEqual(originalCopy);
  708.     });
  709.  
  710.     test('Should produce different orders (probabilistic)', () => {
  711.       const original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  712.       const results = new Set();
  713.      
  714.       // Run shuffle multiple times
  715.       for (let i = 0; i < 20; i++) {
  716.         const shuffled = shuffleArray(original);
  717.         results.add(shuffled.join(','));
  718.       }
  719.      
  720.       // Should have produced at least 2 different orderings
  721.       expect(results.size).toBeGreaterThan(1);
  722.     });
  723.  
  724.     test('Should handle empty array', () => {
  725.       const empty: number[] = [];
  726.       const shuffled = shuffleArray(empty);
  727.      
  728.       expect(shuffled).toEqual([]);
  729.     });
  730.  
  731.     test('Should handle single-element array', () => {
  732.       const single = ['only'];
  733.       const shuffled = shuffleArray(single);
  734.      
  735.       expect(shuffled).toEqual(['only']);
  736.     });
  737.  
  738.     test('Should work with objects', () => {
  739.       const objects = [
  740.         { id: 1, name: 'A' },
  741.         { id: 2, name: 'B' },
  742.         { id: 3, name: 'C' },
  743.       ];
  744.       const shuffled = shuffleArray(objects);
  745.      
  746.       expect(shuffled).toHaveLength(3);
  747.       objects.forEach(obj => {
  748.         expect(shuffled).toContainEqual(obj);
  749.       });
  750.     });
  751.   });
  752.  
  753.   describe('generateTeamColors', () => {
  754.     test('Should generate correct number of colors', () => {
  755.       const colors = generateTeamColors(4);
  756.       expect(colors).toHaveLength(4);
  757.     });
  758.  
  759.     test('Should use predefined colors in order', () => {
  760.       const colors = generateTeamColors(3);
  761.      
  762.       expect(colors[0]).toBe(TEAM_COLORS[0]);
  763.       expect(colors[1]).toBe(TEAM_COLORS[1]);
  764.       expect(colors[2]).toBe(TEAM_COLORS[2]);
  765.     });
  766.  
  767.     test('Should cycle through colors when more teams than colors', () => {
  768.       const teamCount = TEAM_COLORS.length + 3;
  769.       const colors = generateTeamColors(teamCount);
  770.      
  771.       expect(colors).toHaveLength(teamCount);
  772.      
  773.       // Check cycling
  774.       expect(colors[TEAM_COLORS.length]).toBe(TEAM_COLORS[0]);
  775.       expect(colors[TEAM_COLORS.length + 1]).toBe(TEAM_COLORS[1]);
  776.       expect(colors[TEAM_COLORS.length + 2]).toBe(TEAM_COLORS[2]);
  777.     });
  778.  
  779.     test('Should handle zero teams', () => {
  780.       const colors = generateTeamColors(0);
  781.       expect(colors).toEqual([]);
  782.     });
  783.  
  784.     test('Should handle one team', () => {
  785.       const colors = generateTeamColors(1);
  786.       expect(colors).toHaveLength(1);
  787.       expect(colors[0]).toBe(TEAM_COLORS[0]);
  788.     });
  789.  
  790.     test('Should handle large number of teams', () => {
  791.       const colors = generateTeamColors(50);
  792.       expect(colors).toHaveLength(50);
  793.      
  794.       // All should be valid colors from the palette
  795.       colors.forEach((color, index) => {
  796.         const expectedColor = TEAM_COLORS[index % TEAM_COLORS.length];
  797.         expect(color).toBe(expectedColor);
  798.       });
  799.     });
  800.  
  801.     test('Should always return string colors', () => {
  802.       const colors = generateTeamColors(5);
  803.      
  804.       colors.forEach(color => {
  805.         expect(typeof color).toBe('string');
  806.         expect(color.length).toBeGreaterThan(0);
  807.       });
  808.     });
  809.   });
  810.  
  811.   describe('Integration of utilities', () => {
  812.     test('Shuffled team colors should maintain all unique colors', () => {
  813.       const colors = generateTeamColors(TEAM_COLORS.length);
  814.       const shuffledColors = shuffleArray(colors);
  815.      
  816.       // Should have all unique colors
  817.       const uniqueColors = new Set(shuffledColors);
  818.       expect(uniqueColors.size).toBe(TEAM_COLORS.length);
  819.      
  820.       // All original colors should be present
  821.       TEAM_COLORS.forEach(color => {
  822.         expect(shuffledColors).toContain(color);
  823.       });
  824.     });
  825.   });
  826. });
  827. </file>
  828.  
  829. <file path="itemRegistry.test.ts">
  830. import { describe, test, expect, beforeEach } from 'vitest';
  831. import {
  832.   doublePointsItem,
  833.   pointStealItem,
  834.   swapPointsItem,
  835.   givePointsItem,
  836.   twoOptionsItem,
  837.   skipItem,
  838.   gambleItem,
  839.   calculateItemCount,
  840.   assignItemsToQuestions,
  841.   AVAILABLE_ITEMS,
  842.   setActiveItems,
  843.   configureActiveItems,
  844.   getItemById,
  845.   ITEM_REGISTRY,
  846.   ITEM_CATEGORIES,
  847. } from '../TeamTrivia/lib/itemRegistry';
  848. import type { GameQuestion, ItemDensity, GameStateBase } from '../TeamTrivia/lib/types';
  849.  
  850. describe('Item Registry - Power-Up Definitions', () => {
  851.   describe('Item Properties', () => {
  852.     test('Double Points item should have correct properties', () => {
  853.       expect(doublePointsItem.id).toBe('double-points');
  854.       expect(doublePointsItem.name).toBe('Double Points');
  855.       expect(doublePointsItem.category).toBe('yellow');
  856.       expect(doublePointsItem.imageUrl).toBe('⭐');
  857.       expect(doublePointsItem.description).toContain('double points');
  858.     });
  859.  
  860.     test('Point Steal item should have correct properties', () => {
  861.       expect(pointStealItem.id).toBe('point-steal');
  862.       expect(pointStealItem.name).toBe('Point Steal');
  863.       expect(pointStealItem.category).toBe('purple');
  864.       expect(pointStealItem.imageUrl).toBe('⚡');
  865.       expect(pointStealItem.description).toContain('steal');
  866.     });
  867.  
  868.     test('Swap Points item should have correct properties', () => {
  869.       expect(swapPointsItem.id).toBe('swap-points');
  870.       expect(swapPointsItem.name).toBe('Swap Points');
  871.       expect(swapPointsItem.category).toBe('purple');
  872.       expect(swapPointsItem.imageUrl).toBe('🔄');
  873.       expect(swapPointsItem.description).toContain('Exchange');
  874.     });
  875.  
  876.     test('Give Points item should have correct properties', () => {
  877.       expect(givePointsItem.id).toBe('give-points');
  878.       expect(givePointsItem.name).toBe('Give Points');
  879.       expect(givePointsItem.category).toBe('orange');
  880.       expect(givePointsItem.imageUrl).toBe('🎁');
  881.       expect(givePointsItem.description).toContain('Give');
  882.     });
  883.  
  884.     test('Two Options item should have correct properties', () => {
  885.       expect(twoOptionsItem.id).toBe('two-options');
  886.       expect(twoOptionsItem.name).toBe('Two Options');
  887.       expect(twoOptionsItem.category).toBe('green');
  888.       expect(twoOptionsItem.imageUrl).toBe('✌️');
  889.       expect(twoOptionsItem.description).toContain('Removes two incorrect');
  890.     });
  891.  
  892.     test('Skip item should have correct properties', () => {
  893.       expect(skipItem.id).toBe('skip');
  894.       expect(skipItem.name).toBe('Skip Team');
  895.       expect(skipItem.category).toBe('red');
  896.       expect(skipItem.imageUrl).toBe('⏭️');
  897.       expect(skipItem.description).toContain('skip');
  898.     });
  899.  
  900.     test('Gamble item should have correct properties', () => {
  901.       expect(gambleItem.id).toBe('gamble');
  902.       expect(gambleItem.name).toBe('Gamble');
  903.       expect(gambleItem.category).toBe('orange');
  904.       expect(gambleItem.imageUrl).toBe('🎲');
  905.       expect(gambleItem.description).toContain('Bet');
  906.     });
  907.   });
  908.  
  909.   describe('Item Effects', () => {
  910.     const mockGameState: GameStateBase = {
  911.       teams: [
  912.         { id: 0, name: 'Team 1', score: 20, color: 'blue' },
  913.         { id: 1, name: 'Team 2', score: 30, color: 'red' },
  914.       ],
  915.       currentTeamIndex: 0,
  916.     };
  917.  
  918.     test('Double Points effect should return correct multiplier', () => {
  919.       const effect = doublePointsItem.effect(mockGameState);
  920.       expect(effect.multiplyPoints).toBe(2);
  921.       expect(effect.pointsToAward).toBe(20);
  922.     });
  923.  
  924.     test('Point Steal effect should return steal amount', () => {
  925.       const effect = pointStealItem.effect(mockGameState);
  926.       expect(effect.stealPointsAmount).toBe(5);
  927.       expect(effect.requiresTeamSelection).toBe(true);
  928.     });
  929.  
  930.     test('Swap Points effect should require team selection', () => {
  931.       const effect = swapPointsItem.effect(mockGameState);
  932.       expect(effect.swapPointsAmount).toBe(true);
  933.       expect(effect.requiresTeamSelection).toBe(true);
  934.     });
  935.  
  936.     test('Give Points effect should return give amount', () => {
  937.       const effect = givePointsItem.effect(mockGameState);
  938.       expect(effect.givePointsAmount).toBe(5);
  939.       expect(effect.requiresTeamSelection).toBe(true);
  940.     });
  941.  
  942.     test('Two Options effect should return options to remove', () => {
  943.       const effect = twoOptionsItem.effect(mockGameState);
  944.       expect(effect.optionsToRemove).toBe(2);
  945.     });
  946.  
  947.     test('Skip effect should return skip turn flag', () => {
  948.       const effect = skipItem.effect(mockGameState);
  949.       expect(effect.skipTurn).toBe(true);
  950.       expect(effect.requiresTeamSelection).toBe(true);
  951.     });
  952.  
  953.     test('Gamble effect should return gamble flag', () => {
  954.       const effect = gambleItem.effect(mockGameState);
  955.       expect(effect.gamble).toBe(true);
  956.     });
  957.   });
  958. });
  959.  
  960. describe('Item Count Calculation', () => {
  961.   test('Should return 0 for "none" density', () => {
  962.     expect(calculateItemCount('none', 10)).toBe(0);
  963.   });
  964.  
  965.   test('Should return at least 1 for "a-little" density', () => {
  966.     expect(calculateItemCount('a-little', 5)).toBe(1);
  967.     expect(calculateItemCount('a-little', 10)).toBe(1);
  968.     expect(calculateItemCount('a-little', 20)).toBe(2);
  969.   });
  970.  
  971.   test('Should return at least 2 for "some" density', () => {
  972.     expect(calculateItemCount('some', 5)).toBe(2);
  973.     expect(calculateItemCount('some', 10)).toBe(3);
  974.     expect(calculateItemCount('some', 20)).toBe(5);
  975.   });
  976.  
  977.   test('Should return at least 3 for "a-lot" density', () => {
  978.     expect(calculateItemCount('a-lot', 5)).toBe(3);
  979.     expect(calculateItemCount('a-lot', 10)).toBe(4);
  980.     expect(calculateItemCount('a-lot', 20)).toBe(8);
  981.   });
  982.  
  983.   test('Should handle edge cases', () => {
  984.     expect(calculateItemCount('none', 0)).toBe(0);
  985.     expect(calculateItemCount('a-little', 1)).toBe(1);
  986.     expect(calculateItemCount('invalid' as ItemDensity, 10)).toBe(0);
  987.   });
  988. });
  989.  
  990. describe('Item Assignment to Questions', () => {
  991.   let mockQuestions: Partial<GameQuestion>[];
  992.  
  993.   beforeEach(() => {
  994.     mockQuestions = Array(10).fill(null).map((_, i) => ({
  995.       text: `Question ${i}`,
  996.       item: null,
  997.     }));
  998.   });
  999.  
  1000.   test('Should not assign items with "none" density', () => {
  1001.     const result = assignItemsToQuestions(
  1002.       mockQuestions as GameQuestion[],
  1003.       'none'
  1004.     );
  1005.    
  1006.     const itemCount = result.filter(q => q.item !== null).length;
  1007.     expect(itemCount).toBe(0);
  1008.   });
  1009.  
  1010.   test('Should assign correct number of items for "a-little" density', () => {
  1011.     const result = assignItemsToQuestions(
  1012.       mockQuestions as GameQuestion[],
  1013.       'a-little'
  1014.     );
  1015.    
  1016.     const itemCount = result.filter(q => q.item !== null).length;
  1017.     expect(itemCount).toBeGreaterThanOrEqual(1);
  1018.     expect(itemCount).toBeLessThanOrEqual(2);
  1019.   });
  1020.  
  1021.   test('Should assign items randomly', () => {
  1022.     // Run multiple times to check randomness
  1023.     const results = [];
  1024.     for (let i = 0; i < 5; i++) {
  1025.       const result = assignItemsToQuestions(
  1026.         mockQuestions as GameQuestion[],
  1027.         'some'
  1028.       );
  1029.       const indices = result
  1030.         .map((q, i) => q.item ? i : -1)
  1031.         .filter(i => i !== -1);
  1032.       results.push(indices.join(','));
  1033.     }
  1034.    
  1035.     // At least some results should be different
  1036.     const uniqueResults = new Set(results);
  1037.     expect(uniqueResults.size).toBeGreaterThan(1);
  1038.   });
  1039.  
  1040.   test('Should not assign Swap Points in first round', () => {
  1041.     const questions = Array(4).fill(null).map((_, i) => ({
  1042.       text: `Question ${i}`,
  1043.       item: null,
  1044.     }));
  1045.  
  1046.     // Force using only swap points item
  1047.     const itemsToUse = [swapPointsItem];
  1048.    
  1049.     const result = assignItemsToQuestions(
  1050.       questions as GameQuestion[],
  1051.       'a-lot',
  1052.       itemsToUse,
  1053.       4 // 4 teams means first 4 questions are round 1
  1054.     );
  1055.  
  1056.     // No swap points should be in first 4 questions
  1057.     const firstRoundItems = result.slice(0, 4).filter(q =>
  1058.       q.item && q.item.name === 'Swap Points'
  1059.     );
  1060.     expect(firstRoundItems.length).toBe(0);
  1061.   });
  1062.  
  1063.   test('Should handle empty item array', () => {
  1064.     const result = assignItemsToQuestions(
  1065.       mockQuestions as GameQuestion[],
  1066.       'some',
  1067.       [] // Empty items array
  1068.     );
  1069.    
  1070.     const itemCount = result.filter(q => q.item !== null).length;
  1071.     expect(itemCount).toBe(0);
  1072.   });
  1073.  
  1074.   test('Should give each item instance a unique ID', () => {
  1075.     const result = assignItemsToQuestions(
  1076.       mockQuestions as GameQuestion[],
  1077.       'a-lot'
  1078.     );
  1079.    
  1080.     const itemIds = result
  1081.       .filter(q => q.item !== null)
  1082.       .map(q => q.item!.id);
  1083.    
  1084.     const uniqueIds = new Set(itemIds);
  1085.     expect(uniqueIds.size).toBe(itemIds.length);
  1086.   });
  1087. });
  1088.  
  1089. describe('Active Items Configuration', () => {
  1090.   test('setActiveItems should update AVAILABLE_ITEMS', () => {
  1091.    
  1092.     setActiveItems(['double-points', 'point-steal']);
  1093.    
  1094.     expect(AVAILABLE_ITEMS).toHaveLength(2);
  1095.     expect(AVAILABLE_ITEMS[0]?.id).toBe('double-points');
  1096.     expect(AVAILABLE_ITEMS[1]?.id).toBe('point-steal');
  1097.  
  1098.     // Reset
  1099.     setActiveItems(['double-points', 'point-steal', 'swap-points', 'give-points', 'two-options']);
  1100.   });
  1101.  
  1102.   test('configureActiveItems should update based on flags', () => {
  1103.     configureActiveItems({
  1104.       doublePoints: true,
  1105.       pointSteal: false,
  1106.       swapPoints: true,
  1107.       givePoints: false,
  1108.       twoOptions: true,
  1109.       skip: false,
  1110.       gamble: false,
  1111.     });
  1112.  
  1113.     const activeIds = AVAILABLE_ITEMS.map(item => item.id);
  1114.     expect(activeIds).toContain('double-points');
  1115.     expect(activeIds).not.toContain('point-steal');
  1116.     expect(activeIds).toContain('swap-points');
  1117.     expect(activeIds).not.toContain('give-points');
  1118.     expect(activeIds).toContain('two-options');
  1119.  
  1120.     // Reset
  1121.     configureActiveItems({
  1122.       doublePoints: true,
  1123.       pointSteal: true,
  1124.       swapPoints: true,
  1125.       givePoints: true,
  1126.       twoOptions: true,
  1127.       skip: false,
  1128.       gamble: false,
  1129.     });
  1130.   });
  1131.  
  1132.   test('getItemById should return correct item', () => {
  1133.     expect(getItemById('double-points')).toBe(doublePointsItem);
  1134.     expect(getItemById('point-steal')).toBe(pointStealItem);
  1135.     expect(getItemById('invalid-id')).toBeUndefined();
  1136.   });
  1137. });
  1138.  
  1139. describe('Item Categories', () => {
  1140.   test('ITEM_CATEGORIES should group items correctly', () => {
  1141.     expect(ITEM_CATEGORIES.green).toContain(twoOptionsItem);
  1142.     expect(ITEM_CATEGORIES.yellow).toContain(doublePointsItem);
  1143.     expect(ITEM_CATEGORIES.red).toContain(skipItem);
  1144.     expect(ITEM_CATEGORIES.orange).toContain(givePointsItem);
  1145.     expect(ITEM_CATEGORIES.orange).toContain(gambleItem);
  1146.     expect(ITEM_CATEGORIES.purple).toContain(pointStealItem);
  1147.     expect(ITEM_CATEGORIES.purple).toContain(swapPointsItem);
  1148.   });
  1149.  
  1150.   test('All items should be in ITEM_REGISTRY', () => {
  1151.     const registryIds = Object.keys(ITEM_REGISTRY);
  1152.     expect(registryIds).toContain('double-points');
  1153.     expect(registryIds).toContain('point-steal');
  1154.     expect(registryIds).toContain('swap-points');
  1155.     expect(registryIds).toContain('give-points');
  1156.     expect(registryIds).toContain('two-options');
  1157.     expect(registryIds).toContain('skip');
  1158.     expect(registryIds).toContain('gamble');
  1159.   });
  1160. });
  1161.  
  1162. describe('Edge Cases', () => {
  1163.   test('Should handle questions with pre-existing items', () => {
  1164.     const questionsWithItems = Array(5).fill(null).map((_, i) => ({
  1165.       text: `Question ${i}`,
  1166.       item: i === 0 ? doublePointsItem : null,
  1167.     }));
  1168.  
  1169.     const result = assignItemsToQuestions(
  1170.       questionsWithItems as GameQuestion[],
  1171.       'some'
  1172.     );
  1173.  
  1174.     // The function resets all items to null first, then assigns new ones
  1175.     // So pre-existing items are cleared and may or may not get a new item
  1176.     const itemCount = result.filter(q => q.item !== null).length;
  1177.     expect(itemCount).toBeGreaterThanOrEqual(2); // "some" density should have at least 2 items
  1178.   });
  1179.  
  1180.   test('Should handle more items requested than questions available', () => {
  1181.     const fewQuestions = Array(2).fill(null).map((_, i) => ({
  1182.       text: `Question ${i}`,
  1183.       item: null,
  1184.     }));
  1185.  
  1186.     const result = assignItemsToQuestions(
  1187.       fewQuestions as GameQuestion[],
  1188.       'a-lot' // Would request 3+ items but only 2 questions
  1189.     );
  1190.  
  1191.     const itemCount = result.filter(q => q.item !== null).length;
  1192.     expect(itemCount).toBeLessThanOrEqual(2);
  1193.   });
  1194. });
  1195. </file>
  1196.  
  1197. <file path="quizForm.test.tsx">
  1198. import { describe, test, expect } from 'vitest';
  1199. import type { MultipleChoiceQuestion } from '~/lib/types';
  1200.  
  1201. // Mock quiz validation functions
  1202. export const validateQuizQuestion = (question: Partial<MultipleChoiceQuestion>): string[] => {
  1203.   const errors: string[] = [];
  1204.  
  1205.   if (!question.text || question.text.trim().length === 0) {
  1206.     errors.push('Question text is required');
  1207.   }
  1208.  
  1209.   if (!question.correct_answer || question.correct_answer.trim().length === 0) {
  1210.     errors.push('Correct answer is required');
  1211.   }
  1212.  
  1213.   if (!question.options || question.options.length === 0) {
  1214.     errors.push('At least one option is required');
  1215.   } else {
  1216.     const invalidAnswers = question.options.filter(
  1217.       (answer: string) => !answer || answer.trim().length === 0
  1218.     );
  1219.     if (invalidAnswers.length > 0) {
  1220.       errors.push('All options must have text');
  1221.     }
  1222.   }
  1223.  
  1224.   // Check for duplicate answers
  1225.   if (question.correct_answer && question.options) {
  1226.     const allAnswers = question.options;
  1227.     const uniqueAnswers = new Set(allAnswers.map((a: string) => a.trim().toLowerCase()));
  1228.     if (uniqueAnswers.size < allAnswers.length) {
  1229.       errors.push('All options must be unique');
  1230.     }
  1231.    
  1232.     // Check that correct answer is among options
  1233.     if (!question.options.includes(question.correct_answer)) {
  1234.       errors.push('Correct answer must be one of the options');
  1235.     }
  1236.   }
  1237.  
  1238.   return errors;
  1239. };
  1240.  
  1241. export const formatQuizQuestion = (question: Partial<MultipleChoiceQuestion>): MultipleChoiceQuestion => {
  1242.   const formatted: any = {
  1243.     text: question.text?.trim() || '',
  1244.     correct_answer: question.correct_answer?.trim() || '',
  1245.     options: question.options?.map((a: string) => a.trim()).filter((a: string) => a.length > 0) || [],
  1246.   };
  1247.  
  1248.   if (question.explanation?.trim()) {
  1249.     formatted.explanation = question.explanation.trim();
  1250.   }
  1251.  
  1252.   return formatted;
  1253. };
  1254.  
  1255. export const shuffleAnswers = (question: MultipleChoiceQuestion): string[] => {
  1256.   const allAnswers = [...question.options];
  1257.  
  1258.   // Fisher-Yates shuffle
  1259.   const shuffled = [...allAnswers];
  1260.   for (let i = shuffled.length - 1; i > 0; i--) {
  1261.     const j = Math.floor(Math.random() * (i + 1));
  1262.     [shuffled[i], shuffled[j]] = [shuffled[j]!, shuffled[i]!];
  1263.   }
  1264.  
  1265.   return shuffled;
  1266. };
  1267.  
  1268. describe('Quiz Form Validation', () => {
  1269.   describe('validateQuizQuestion', () => {
  1270.     test('Should validate required question text', () => {
  1271.       const question: Partial<MultipleChoiceQuestion> = {
  1272.         correct_answer: 'Answer',
  1273.         options: ['Answer', 'Wrong'],
  1274.       };
  1275.      
  1276.       const errors = validateQuizQuestion(question);
  1277.       expect(errors).toContain('Question text is required');
  1278.     });
  1279.    
  1280.     test('Should validate empty question text', () => {
  1281.       const question: Partial<MultipleChoiceQuestion> = {
  1282.         text: '   ',
  1283.         correct_answer: 'Answer',
  1284.         options: ['Answer', 'Wrong'],
  1285.       };
  1286.      
  1287.       const errors = validateQuizQuestion(question);
  1288.       expect(errors).toContain('Question text is required');
  1289.     });
  1290.    
  1291.     test('Should validate required correct answer', () => {
  1292.       const question: Partial<MultipleChoiceQuestion> = {
  1293.         text: 'Question?',
  1294.         options: ['Wrong'],
  1295.       };
  1296.      
  1297.       const errors = validateQuizQuestion(question);
  1298.       expect(errors).toContain('Correct answer is required');
  1299.     });
  1300.    
  1301.     test('Should validate required options', () => {
  1302.       const question: Partial<MultipleChoiceQuestion> = {
  1303.         text: 'Question?',
  1304.         correct_answer: 'Answer',
  1305.         options: [],
  1306.       };
  1307.      
  1308.       const errors = validateQuizQuestion(question);
  1309.       expect(errors).toContain('At least one option is required');
  1310.     });
  1311.    
  1312.     test('Should validate empty options', () => {
  1313.       const question: Partial<MultipleChoiceQuestion> = {
  1314.         text: 'Question?',
  1315.         correct_answer: 'Answer',
  1316.         options: ['', 'Valid', '   '],
  1317.       };
  1318.      
  1319.       const errors = validateQuizQuestion(question);
  1320.       expect(errors).toContain('All options must have text');
  1321.     });
  1322.    
  1323.     test('Should detect duplicate options', () => {
  1324.       const question: Partial<MultipleChoiceQuestion> = {
  1325.         text: 'Question?',
  1326.         correct_answer: 'Answer',
  1327.         options: ['Answer', 'Answer', 'Other'],
  1328.       };
  1329.      
  1330.       const errors = validateQuizQuestion(question);
  1331.       expect(errors).toContain('All options must be unique');
  1332.     });
  1333.    
  1334.     test('Should detect duplicate options case-insensitive', () => {
  1335.       const question: Partial<MultipleChoiceQuestion> = {
  1336.         text: 'Question?',
  1337.         correct_answer: 'ANSWER',
  1338.         options: ['ANSWER', 'answer', 'Other'],
  1339.       };
  1340.      
  1341.       const errors = validateQuizQuestion(question);
  1342.       expect(errors).toContain('All options must be unique');
  1343.     });
  1344.    
  1345.     test('Should pass validation for valid question', () => {
  1346.       const question: Partial<MultipleChoiceQuestion> = {
  1347.         text: 'What is 2 + 2?',
  1348.         correct_answer: '4',
  1349.         options: ['3', '4', '5', '6'],
  1350.         explanation: 'Basic math',
  1351.       };
  1352.      
  1353.       const errors = validateQuizQuestion(question);
  1354.       expect(errors).toHaveLength(0);
  1355.     });
  1356.    
  1357.     test('Should handle multiple errors', () => {
  1358.       const question: Partial<MultipleChoiceQuestion> = {
  1359.         text: '',
  1360.         correct_answer: '',
  1361.         options: [],
  1362.       };
  1363.      
  1364.       const errors = validateQuizQuestion(question);
  1365.       expect(errors.length).toBeGreaterThan(1);
  1366.       expect(errors).toContain('Question text is required');
  1367.       expect(errors).toContain('Correct answer is required');
  1368.       expect(errors).toContain('At least one option is required');
  1369.     });
  1370.   });
  1371.  
  1372.   describe('formatQuizQuestion', () => {
  1373.     test('Should trim whitespace from all fields', () => {
  1374.       const question: Partial<MultipleChoiceQuestion> = {
  1375.         text: '  Question?  ',
  1376.         correct_answer: '  Answer  ',
  1377.         options: ['  Answer  ', '  Wrong 1  ', '  Wrong 2  '],
  1378.         explanation: '  Explanation  ',
  1379.       };
  1380.      
  1381.       const formatted = formatQuizQuestion(question);
  1382.      
  1383.       expect(formatted.text).toBe('Question?');
  1384.       expect(formatted.correct_answer).toBe('Answer');
  1385.       expect(formatted.options).toEqual(['Answer', 'Wrong 1', 'Wrong 2']);
  1386.       expect(formatted.explanation).toBe('Explanation');
  1387.     });
  1388.    
  1389.     test('Should filter empty options', () => {
  1390.       const question: Partial<MultipleChoiceQuestion> = {
  1391.         text: 'Question?',
  1392.         correct_answer: 'Answer',
  1393.         options: ['Answer', 'Wrong 1', '', '   ', 'Wrong 2'],
  1394.       };
  1395.      
  1396.       const formatted = formatQuizQuestion(question);
  1397.      
  1398.       expect(formatted.options).toEqual(['Answer', 'Wrong 1', 'Wrong 2']);
  1399.     });
  1400.    
  1401.     test('Should handle missing fields with defaults', () => {
  1402.       const question: Partial<MultipleChoiceQuestion> = {};
  1403.      
  1404.       const formatted = formatQuizQuestion(question);
  1405.      
  1406.       expect(formatted.text).toBe('');
  1407.       expect(formatted.correct_answer).toBe('');
  1408.       expect(formatted.options).toEqual([]);
  1409.       expect(formatted.explanation).toBeUndefined();
  1410.     });
  1411.    
  1412.     test('Should preserve valid data', () => {
  1413.       const question: Partial<MultipleChoiceQuestion> = {
  1414.         text: 'Valid Question',
  1415.         correct_answer: 'Valid Answer',
  1416.         options: ['Valid Answer', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
  1417.         explanation: 'Valid Explanation',
  1418.       };
  1419.      
  1420.       const formatted = formatQuizQuestion(question);
  1421.      
  1422.       expect(formatted).toEqual({
  1423.         text: 'Valid Question',
  1424.         correct_answer: 'Valid Answer',
  1425.         options: ['Valid Answer', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
  1426.         explanation: 'Valid Explanation',
  1427.       });
  1428.     });
  1429.   });
  1430.  
  1431.   describe('shuffleAnswers', () => {
  1432.     test('Should contain all answers', () => {
  1433.       const question: MultipleChoiceQuestion = {
  1434.         text: 'Question',
  1435.         correct_answer: 'Correct',
  1436.         options: ['Correct', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
  1437.       };
  1438.      
  1439.       const shuffled = shuffleAnswers(question);
  1440.      
  1441.       expect(shuffled).toHaveLength(4);
  1442.       expect(shuffled).toContain('Correct');
  1443.       expect(shuffled).toContain('Wrong 1');
  1444.       expect(shuffled).toContain('Wrong 2');
  1445.       expect(shuffled).toContain('Wrong 3');
  1446.     });
  1447.    
  1448.     test('Should produce different orders (probabilistic)', () => {
  1449.       const question: MultipleChoiceQuestion = {
  1450.         text: 'Question',
  1451.         correct_answer: 'A',
  1452.         options: ['A', 'B', 'C', 'D', 'E', 'F'],
  1453.       };
  1454.      
  1455.       const results = new Set<string>();
  1456.      
  1457.       // Run shuffle multiple times
  1458.       for (let i = 0; i < 20; i++) {
  1459.         const shuffled = shuffleAnswers(question);
  1460.         results.add(shuffled.join(','));
  1461.       }
  1462.      
  1463.       // Should have produced multiple different orderings
  1464.       expect(results.size).toBeGreaterThan(1);
  1465.     });
  1466.    
  1467.     test('Should handle question with two options', () => {
  1468.       const question: MultipleChoiceQuestion = {
  1469.         text: 'Question',
  1470.         correct_answer: 'Yes',
  1471.         options: ['Yes', 'No'],
  1472.       };
  1473.      
  1474.       const shuffled = shuffleAnswers(question);
  1475.      
  1476.       expect(shuffled).toHaveLength(2);
  1477.       expect(shuffled).toContain('Yes');
  1478.       expect(shuffled).toContain('No');
  1479.     });
  1480.   });
  1481. });
  1482.  
  1483. describe('Quiz Data Management', () => {
  1484.   describe('Quiz Creation', () => {
  1485.     test('Should create new quiz with default values', () => {
  1486.       const newQuiz = {
  1487.         id: 'quiz-1',
  1488.         questions: [],
  1489.         createdAt: new Date().toISOString(),
  1490.         updatedAt: new Date().toISOString(),
  1491.       };
  1492.      
  1493.       expect(newQuiz.questions).toHaveLength(0);
  1494.       expect(newQuiz.id).toBeDefined();
  1495.       expect(newQuiz.createdAt).toBeDefined();
  1496.     });
  1497.    
  1498.     test('Should add question to quiz', () => {
  1499.       const quiz = {
  1500.         id: 'quiz-1',
  1501.         questions: [] as MultipleChoiceQuestion[],
  1502.       };
  1503.      
  1504.       const question: MultipleChoiceQuestion = {
  1505.         text: 'New Question',
  1506.         correct_answer: 'Answer',
  1507.         options: ['Answer', 'Wrong'],
  1508.       };
  1509.      
  1510.       quiz.questions.push(question);
  1511.      
  1512.       expect(quiz.questions).toHaveLength(1);
  1513.       expect(quiz.questions[0]).toEqual(question);
  1514.     });
  1515.    
  1516.     test('Should remove question from quiz', () => {
  1517.       const quiz = {
  1518.         questions: [
  1519.           { text: 'Q1', correct_answer: 'A1', options: ['A1', 'W1'] },
  1520.           { text: 'Q2', correct_answer: 'A2', options: ['A2', 'W2'] },
  1521.           { text: 'Q3', correct_answer: 'A3', options: ['A3', 'W3'] },
  1522.         ] as MultipleChoiceQuestion[],
  1523.       };
  1524.      
  1525.       quiz.questions.splice(1, 1); // Remove Q2
  1526.      
  1527.       expect(quiz.questions).toHaveLength(2);
  1528.       expect(quiz.questions[0]!.text).toBe('Q1');
  1529.       expect(quiz.questions[1]!.text).toBe('Q3');
  1530.     });
  1531.    
  1532.     test('Should update question in quiz', () => {
  1533.       const quiz = {
  1534.         questions: [
  1535.           { text: 'Original', correct_answer: 'A', options: ['A', 'W'] },
  1536.         ] as MultipleChoiceQuestion[],
  1537.       };
  1538.      
  1539.       quiz.questions[0] = {
  1540.         text: 'Updated',
  1541.         correct_answer: 'B',
  1542.         options: ['B', 'X', 'Y'],
  1543.       };
  1544.      
  1545.       expect(quiz.questions[0]!.text).toBe('Updated');
  1546.       expect(quiz.questions[0]!.correct_answer).toBe('B');
  1547.       expect(quiz.questions[0]!.options).toEqual(['B', 'X', 'Y']);
  1548.     });
  1549.   });
  1550.  
  1551.   describe('Quiz Statistics', () => {
  1552.     test('Should calculate total questions', () => {
  1553.       const quiz = {
  1554.         questions: [
  1555.           { text: 'Q1', correct_answer: 'A', options: ['A', 'W'] },
  1556.           { text: 'Q2', correct_answer: 'A', options: ['A', 'W'] },
  1557.           { text: 'Q3', correct_answer: 'A', options: ['A', 'W'] },
  1558.         ],
  1559.       };
  1560.      
  1561.       expect(quiz.questions.length).toBe(3);
  1562.     });
  1563.    
  1564.     test('Should calculate average options per question', () => {
  1565.       const quiz = {
  1566.         questions: [
  1567.           { text: 'Q1', correct_answer: 'A', options: ['A', 'B', 'C', 'D'] },
  1568.           { text: 'Q2', correct_answer: 'A', options: ['A', 'B', 'C'] },
  1569.           { text: 'Q3', correct_answer: 'A', options: ['A', 'B', 'C', 'D', 'E'] },
  1570.         ] as MultipleChoiceQuestion[],
  1571.       };
  1572.      
  1573.       const totalOptions = quiz.questions.reduce(
  1574.         (sum, q) => sum + q.options.length,
  1575.         0
  1576.       );
  1577.       const avgOptions = totalOptions / quiz.questions.length;
  1578.      
  1579.       expect(avgOptions).toBe(4); // (4 + 3 + 5) / 3 = 4
  1580.     });
  1581.    
  1582.     test('Should count questions with explanations', () => {
  1583.       const quiz = {
  1584.         questions: [
  1585.           { text: 'Q1', correct_answer: 'A', options: ['A', 'B'], explanation: 'E1' },
  1586.           { text: 'Q2', correct_answer: 'A', options: ['A', 'B'] },
  1587.           { text: 'Q3', correct_answer: 'A', options: ['A', 'B'], explanation: 'E3' },
  1588.         ] as MultipleChoiceQuestion[],
  1589.       };
  1590.      
  1591.       const withExplanations = quiz.questions.filter(q => q.explanation).length;
  1592.      
  1593.       expect(withExplanations).toBe(2);
  1594.     });
  1595.   });
  1596.  
  1597.   describe('Quiz Duplication', () => {
  1598.     test('Should deep clone quiz questions', () => {
  1599.       const original: MultipleChoiceQuestion[] = [
  1600.         { text: 'Q1', correct_answer: 'A', options: ['A', 'B', 'C'] },
  1601.       ];
  1602.      
  1603.       const cloned = JSON.parse(JSON.stringify(original)) as MultipleChoiceQuestion[];
  1604.      
  1605.       // Modify cloned
  1606.       cloned[0]!.text = 'Modified';
  1607.       cloned[0]!.options.push('D');
  1608.      
  1609.       // Original should be unchanged
  1610.       expect(original[0]!.text).toBe('Q1');
  1611.       expect(original[0]!.options).toHaveLength(3);
  1612.     });
  1613.   });
  1614. });
  1615. </file>
  1616.  
  1617. <file path="quizIntegration.test.ts">
  1618. import { describe, test, expect, beforeEach } from 'vitest';
  1619. import { server } from '~/test-utils/mocks/server';
  1620. import { http, HttpResponse } from 'msw';
  1621. import type { MultipleChoiceQuestion } from '~/lib/types';
  1622.  
  1623. describe('Quiz API Integration Tests with MSW', () => {
  1624.   beforeEach(() => {
  1625.     // Reset handlers to default state before each test
  1626.     server.resetHandlers();
  1627.   });
  1628.  
  1629.   describe('Quiz Generation Flow', () => {
  1630.     test('Should generate quiz with specific question count', async () => {
  1631.       let capturedRequest: Request | null = null;
  1632.  
  1633.       // Override handler to capture request details
  1634.       server.use(
  1635.         http.post('/generateQuiz', async ({ request }) => {
  1636.           capturedRequest = request;
  1637.           const formData = await request.formData();
  1638.           const questionCount = parseInt(formData.get('questionCount') as string) || 5;
  1639.  
  1640.           const questions: MultipleChoiceQuestion[] = Array.from(
  1641.             { length: questionCount },
  1642.             (_, i) => ({
  1643.               text: `Generated Question ${i + 1}`,
  1644.               correct_answer: `Answer ${i % 4 + 1}`,
  1645.               options: [`Answer 1`, `Answer 2`, `Answer 3`, `Answer 4`],
  1646.               explanation: `This is explanation ${i + 1}`,
  1647.             })
  1648.           );
  1649.  
  1650.           return HttpResponse.json({
  1651.             success: true,
  1652.             quiz: {
  1653.               id: 'test-quiz-id',
  1654.               multiple_choice: questions,
  1655.               created_at: new Date().toISOString(),
  1656.             },
  1657.           });
  1658.         })
  1659.       );
  1660.  
  1661.       // Simulate the API call
  1662.       const formData = new FormData();
  1663.       formData.append('lessonId', 'test-lesson');
  1664.       formData.append('questionCount', '10');
  1665.       formData.append('includeKnowledgeBase', 'true');
  1666.  
  1667.       const response = await fetch('/generateQuiz', {
  1668.         method: 'POST',
  1669.         body: formData,
  1670.       });
  1671.  
  1672.       const result = await response.json();
  1673.  
  1674.       // Assertions
  1675.       expect(result.success).toBe(true);
  1676.       expect(result.quiz.multiple_choice).toHaveLength(10);
  1677.       expect(result.quiz.id).toBe('test-quiz-id');
  1678.       expect(capturedRequest).not.toBeNull();
  1679.     });
  1680.  
  1681.     test('Should merge existing questions when generating new ones', async () => {
  1682.       const existingQuestions: MultipleChoiceQuestion[] = [
  1683.         {
  1684.           text: 'Existing Question 1',
  1685.           correct_answer: 'Existing Answer',
  1686.           options: ['Existing Answer', 'Wrong 1', 'Wrong 2', 'Wrong 3'],
  1687.         },
  1688.       ];
  1689.  
  1690.       server.use(
  1691.         http.post('/generateQuiz', async ({ request }) => {
  1692.           const formData = await request.formData();
  1693.           const existingQuestionsStr = formData.get('existingQuestions') as string;
  1694.           const existing = existingQuestionsStr ? JSON.parse(existingQuestionsStr) : [];
  1695.           const questionCount = parseInt(formData.get('questionCount') as string) || 5;
  1696.  
  1697.           const newQuestions: MultipleChoiceQuestion[] = Array.from(
  1698.             { length: questionCount },
  1699.             (_, i) => ({
  1700.               text: `New Question ${i + 1}`,
  1701.               correct_answer: 'A',
  1702.               options: ['A', 'B', 'C', 'D'],
  1703.             })
  1704.           );
  1705.  
  1706.           return HttpResponse.json({
  1707.             success: true,
  1708.             quiz: {
  1709.               id: 'merged-quiz',
  1710.               multiple_choice: [...existing, ...newQuestions],
  1711.             },
  1712.           });
  1713.         })
  1714.       );
  1715.  
  1716.       const formData = new FormData();
  1717.       formData.append('existingQuestions', JSON.stringify(existingQuestions));
  1718.       formData.append('questionCount', '3');
  1719.  
  1720.       const response = await fetch('/generateQuiz', {
  1721.         method: 'POST',
  1722.         body: formData,
  1723.       });
  1724.  
  1725.       const result = await response.json();
  1726.  
  1727.       expect(result.success).toBe(true);
  1728.       expect(result.quiz.multiple_choice).toHaveLength(4); // 1 existing + 3 new
  1729.       expect(result.quiz.multiple_choice[0].text).toBe('Existing Question 1');
  1730.     });
  1731.   });
  1732.  
  1733.   describe('Error Scenarios', () => {
  1734.     test('Should handle server errors gracefully', async () => {
  1735.       server.use(
  1736.         http.post('/generateQuiz', () => {
  1737.           return HttpResponse.json(
  1738.             { success: false, error: 'Internal server error' },
  1739.             { status: 500 }
  1740.           );
  1741.         })
  1742.       );
  1743.  
  1744.       const response = await fetch('/generateQuiz', {
  1745.         method: 'POST',
  1746.         body: new FormData(),
  1747.       });
  1748.  
  1749.       const result = await response.json();
  1750.  
  1751.       expect(response.status).toBe(500);
  1752.       expect(result.success).toBe(false);
  1753.       expect(result.error).toBe('Internal server error');
  1754.     });
  1755.  
  1756.     test('Should handle network timeouts', async () => {
  1757.       server.use(
  1758.         http.post('/generateQuiz', async () => {
  1759.           // Simulate a timeout
  1760.           await new Promise(resolve => setTimeout(resolve, 1000));
  1761.           return HttpResponse.json({ success: true });
  1762.         })
  1763.       );
  1764.  
  1765.       const controller = new AbortController();
  1766.       const timeoutId = setTimeout(() => controller.abort(), 100);
  1767.  
  1768.       try {
  1769.         await fetch('/generateQuiz', {
  1770.           method: 'POST',
  1771.           body: new FormData(),
  1772.           signal: controller.signal,
  1773.         });
  1774.         clearTimeout(timeoutId);
  1775.         expect.fail('Should have aborted');
  1776.       } catch (error: any) {
  1777.         clearTimeout(timeoutId);
  1778.         expect(error.name).toBe('AbortError');
  1779.       }
  1780.     });
  1781.  
  1782.     test('Should handle malformed responses', async () => {
  1783.       server.use(
  1784.         http.post('/generateQuiz', () => {
  1785.           return new HttpResponse('Invalid JSON', {
  1786.             status: 200,
  1787.             headers: { 'Content-Type': 'application/json' },
  1788.           });
  1789.         })
  1790.       );
  1791.  
  1792.       const response = await fetch('/generateQuiz', {
  1793.         method: 'POST',
  1794.         body: new FormData(),
  1795.       });
  1796.  
  1797.       try {
  1798.         await response.json();
  1799.         expect.fail('Should have thrown JSON parse error');
  1800.       } catch (error) {
  1801.         expect(error).toBeDefined();
  1802.       }
  1803.     });
  1804.   });
  1805.  
  1806.   describe('Concurrent Requests', () => {
  1807.     test('Should handle multiple concurrent quiz generations', async () => {
  1808.       let requestCount = 0;
  1809.  
  1810.       server.use(
  1811.         http.post('/generateQuiz', async () => {
  1812.           requestCount++;
  1813.           const requestId = requestCount;
  1814.  
  1815.           // Simulate varying processing times
  1816.           await new Promise(resolve => setTimeout(resolve, Math.random() * 100));
  1817.  
  1818.           return HttpResponse.json({
  1819.             success: true,
  1820.             quiz: {
  1821.               id: `quiz-${requestId}`,
  1822.               multiple_choice: [
  1823.                 {
  1824.                   text: `Question from request ${requestId}`,
  1825.                   correct_answer: 'A',
  1826.                   options: ['A', 'B', 'C', 'D'],
  1827.                 },
  1828.               ],
  1829.             },
  1830.           });
  1831.         })
  1832.       );
  1833.  
  1834.       // Make multiple concurrent requests
  1835.       const requests = Array.from({ length: 5 }, (_, i) => {
  1836.         const formData = new FormData();
  1837.         formData.append('lessonId', `lesson-${i}`);
  1838.         return fetch('/generateQuiz', { method: 'POST', body: formData });
  1839.       });
  1840.  
  1841.       const responses = await Promise.all(requests);
  1842.       const results = await Promise.all(responses.map(r => r.json()));
  1843.  
  1844.       // All requests should succeed
  1845.       results.forEach((result) => {
  1846.         expect(result.success).toBe(true);
  1847.         expect(result.quiz.id).toMatch(/^quiz-\d+$/);
  1848.       });
  1849.  
  1850.       expect(requestCount).toBe(5);
  1851.     });
  1852.   });
  1853.  
  1854.   describe('Request Validation', () => {
  1855.     test('Should validate required fields', async () => {
  1856.       server.use(
  1857.         http.post('/generateQuiz', async ({ request }) => {
  1858.           const formData = await request.formData();
  1859.           const lessonId = formData.get('lessonId');
  1860.  
  1861.           if (!lessonId) {
  1862.             return HttpResponse.json(
  1863.               { success: false, error: 'Lesson ID is required' },
  1864.               { status: 400 }
  1865.             );
  1866.           }
  1867.  
  1868.           return HttpResponse.json({ success: true, quiz: { id: 'valid-quiz' } });
  1869.         })
  1870.       );
  1871.  
  1872.       // Request without lessonId
  1873.       const response1 = await fetch('/generateQuiz', {
  1874.         method: 'POST',
  1875.         body: new FormData(),
  1876.       });
  1877.       const result1 = await response1.json();
  1878.       expect(result1.success).toBe(false);
  1879.       expect(result1.error).toBe('Lesson ID is required');
  1880.  
  1881.       // Request with lessonId
  1882.       const formData = new FormData();
  1883.       formData.append('lessonId', 'test-lesson');
  1884.       const response2 = await fetch('/generateQuiz', {
  1885.         method: 'POST',
  1886.         body: formData,
  1887.       });
  1888.       const result2 = await response2.json();
  1889.       expect(result2.success).toBe(true);
  1890.     });
  1891.   });
  1892. });
  1893. </file>
  1894.  
  1895. <file path="teamTriviaReducer.test.ts">
  1896. import { describe, test, expect, beforeEach } from 'vitest';
  1897. import { teamTriviaReducer, initialState, BASE_POINTS } from '../TeamTrivia/context/teamTriviaReducer';
  1898. import type { TeamTriviaContextState } from '../TeamTrivia/context/teamTriviaContext';
  1899. import type { GameQuestion } from '../TeamTrivia/lib/types';
  1900. import { doublePointsItem, pointStealItem, givePointsItem } from '../TeamTrivia/lib/itemRegistry';
  1901.  
  1902. describe('teamTriviaReducer - Core Game Logic', () => {
  1903.   let state: TeamTriviaContextState;
  1904.  
  1905.   beforeEach(() => {
  1906.     state = { ...initialState };
  1907.   });
  1908.  
  1909.   describe('Game Setup Actions', () => {
  1910.     test('SET_NUM_TEAMS should update number of teams', () => {
  1911.       const newState = teamTriviaReducer(state, {
  1912.         type: 'SET_NUM_TEAMS',
  1913.         payload: 4,
  1914.       });
  1915.  
  1916.       expect(newState.numTeams).toBe(4);
  1917.     });
  1918.  
  1919.     test('SET_NUM_QUESTIONS should update number of questions', () => {
  1920.       const newState = teamTriviaReducer(state, {
  1921.         type: 'SET_NUM_QUESTIONS',
  1922.         payload: 10,
  1923.       });
  1924.  
  1925.       expect(newState.selectedNumQuestions).toBe(10);
  1926.     });
  1927.  
  1928.     test('SET_SHOW_OPTIONS should toggle options visibility', () => {
  1929.       expect(state.showOptions).toBe(true);
  1930.  
  1931.       const newState = teamTriviaReducer(state, {
  1932.         type: 'SET_SHOW_OPTIONS',
  1933.         payload: false,
  1934.       });
  1935.  
  1936.       expect(newState.showOptions).toBe(false);
  1937.     });
  1938.  
  1939.     test('SET_SHOW_EXPLANATIONS should toggle explanations visibility', () => {
  1940.       expect(state.showExplanations).toBe(true);
  1941.  
  1942.       const newState = teamTriviaReducer(state, {
  1943.         type: 'SET_SHOW_EXPLANATIONS',
  1944.         payload: false,
  1945.       });
  1946.  
  1947.       expect(newState.showExplanations).toBe(false);
  1948.     });
  1949.  
  1950.     test('SET_ITEM_DENSITY should update item density', () => {
  1951.       expect(state.itemDensity).toBe('none');
  1952.  
  1953.       const newState = teamTriviaReducer(state, {
  1954.         type: 'SET_ITEM_DENSITY',
  1955.         payload: 'some',
  1956.       });
  1957.  
  1958.       expect(newState.itemDensity).toBe('some');
  1959.     });
  1960.   });
  1961.  
  1962.   describe('Game Initialization', () => {
  1963.     const mockQuestions = [
  1964.       {
  1965.         text: 'Question 1',
  1966.         correct_answer: 'A',
  1967.         options: ['A', 'B', 'C', 'D'],
  1968.         explanation: 'Explanation 1',
  1969.       },
  1970.       {
  1971.         text: 'Question 2',
  1972.         correct_answer: 'B',
  1973.         options: ['A', 'B', 'C', 'D'],
  1974.         explanation: 'Explanation 2',
  1975.       },
  1976.     ];
  1977.  
  1978.     test('INITIALIZE_GAME should set up teams and questions', () => {
  1979.       state.numTeams = 3;
  1980.       state.selectedNumQuestions = 2;
  1981.  
  1982.       const newState = teamTriviaReducer(state, {
  1983.         type: 'INITIALIZE_GAME',
  1984.         payload: {
  1985.           questions: mockQuestions,
  1986.           itemDensity: 'none',
  1987.         },
  1988.       });
  1989.  
  1990.       expect(newState.gameState).toBe('playing');
  1991.       expect(newState.teams).toHaveLength(3);
  1992.       expect(newState.gameQuestions).toHaveLength(2);
  1993.       expect(newState.currentTeamIndex).toBe(0);
  1994.       expect(newState.isEndingEarly).toBe(false);
  1995.  
  1996.       // Check team initialization
  1997.       newState.teams.forEach((team, index) => {
  1998.         expect(team.id).toBe(index);
  1999.         expect(team.name).toBe(`Team ${index + 1}`);
  2000.         expect(team.score).toBe(0);
  2001.         expect(team.color).toBeDefined();
  2002.       });
  2003.  
  2004.       // Check question initialization
  2005.       newState.gameQuestions.forEach((question) => {
  2006.         expect(question.revealed).toBe(false);
  2007.         expect(question.answerResult).toBe(null);
  2008.         expect(question.item).toBe(null);
  2009.       });
  2010.     });
  2011.  
  2012.     test('INITIALIZE_GAME should assign items based on density', () => {
  2013.       state.numTeams = 2;
  2014.       state.selectedNumQuestions = 2;
  2015.  
  2016.       const newState = teamTriviaReducer(state, {
  2017.         type: 'INITIALIZE_GAME',
  2018.         payload: {
  2019.           questions: mockQuestions,
  2020.           itemDensity: 'a-lot',
  2021.         },
  2022.       });
  2023.  
  2024.       // With "a-lot" density and 2 questions, at least one should have an item
  2025.       const hasItems = newState.gameQuestions.some((q) => q.item !== null);
  2026.       expect(hasItems).toBe(true);
  2027.     });
  2028.   });
  2029.  
  2030.   describe('Question Selection and Answering', () => {
  2031.     beforeEach(() => {
  2032.       // Set up a basic game state
  2033.       state = {
  2034.         ...state,
  2035.         gameState: 'playing',
  2036.         teams: [
  2037.           { id: 0, name: 'Team 1', score: 0, color: 'blue' },
  2038.           { id: 1, name: 'Team 2', score: 0, color: 'red' },
  2039.         ],
  2040.         gameQuestions: [
  2041.           {
  2042.             text: 'Question 1',
  2043.             correct_answer: 'A',
  2044.             options: ['A', 'B', 'C', 'D'],
  2045.             revealed: false,
  2046.             answerResult: null,
  2047.             item: null,
  2048.             explanation: 'Explanation',
  2049.           } as GameQuestion,
  2050.         ],
  2051.         currentTeamIndex: 0,
  2052.       };
  2053.     });
  2054.  
  2055.     test('SELECT_QUESTION should set current question', () => {
  2056.       const newState = teamTriviaReducer(state, {
  2057.         type: 'SELECT_QUESTION',
  2058.         payload: 0,
  2059.       });
  2060.  
  2061.       expect(newState.currentQuestion).toBe(state.gameQuestions[0]);
  2062.       expect(newState.gameState).toBe('question_view');
  2063.       expect(newState.selectedAnswer).toBe(null);
  2064.       expect(newState.isAnswerCorrect).toBe(null);
  2065.     });
  2066.  
  2067.     test('SELECT_QUESTION should handle questions with items', () => {
  2068.       state.gameQuestions[0]!.item = doublePointsItem;
  2069.  
  2070.       const newState = teamTriviaReducer(state, {
  2071.         type: 'SELECT_QUESTION',
  2072.         payload: 0,
  2073.       });
  2074.  
  2075.       expect(newState.currentItemForReveal).toBe(doublePointsItem);
  2076.       expect(newState.gameState).toBe('item_view');
  2077.       expect(newState.showItemAfterAnswer).toBe(true);
  2078.     });
  2079.  
  2080.     test('SELECT_ANSWER should update selected answer', () => {
  2081.       state.gameState = 'question_view';
  2082.  
  2083.       const newState = teamTriviaReducer(state, {
  2084.         type: 'SELECT_ANSWER',
  2085.         payload: 'A',
  2086.       });
  2087.  
  2088.       expect(newState.selectedAnswer).toBe('A');
  2089.     });
  2090.  
  2091.     test('CHECK_ANSWER should award points for correct answer', () => {
  2092.       state.gameState = 'question_view';
  2093.       state.currentQuestion = state.gameQuestions[0]!;
  2094.       state.selectedAnswer = 'A';
  2095.  
  2096.       const newState = teamTriviaReducer(state, {
  2097.         type: 'CHECK_ANSWER',
  2098.       });
  2099.  
  2100.       expect(newState.isAnswerCorrect).toBe(true);
  2101.       expect(newState.teams[0]!.score).toBe(BASE_POINTS);
  2102.       expect(newState.gameState).toBe('answer_view');
  2103.       expect(newState.gameQuestions[0]!.revealed).toBe(true);
  2104.       expect(newState.gameQuestions[0]!.answerResult).toBe('correct');
  2105.     });
  2106.  
  2107.     test('CHECK_ANSWER should not award points for incorrect answer', () => {
  2108.       state.gameState = 'question_view';
  2109.       state.currentQuestion = state.gameQuestions[0]!;
  2110.       state.selectedAnswer = 'B';
  2111.  
  2112.       const newState = teamTriviaReducer(state, {
  2113.         type: 'CHECK_ANSWER',
  2114.       });
  2115.  
  2116.       expect(newState.isAnswerCorrect).toBe(false);
  2117.       expect(newState.teams[0]!.score).toBe(0);
  2118.       expect(newState.gameState).toBe('answer_view');
  2119.       expect(newState.gameQuestions[0]!.answerResult).toBe('incorrect');
  2120.     });
  2121.   });
  2122.  
  2123.   describe('Power-Up Effects - Double Points', () => {
  2124.     beforeEach(() => {
  2125.       state = {
  2126.         ...state,
  2127.         gameState: 'question_view',
  2128.         teams: [
  2129.           { id: 0, name: 'Team 1', score: 0, color: 'blue' },
  2130.         ],
  2131.         currentTeamIndex: 0,
  2132.         currentQuestion: {
  2133.           text: 'Question',
  2134.           correct_answer: 'A',
  2135.           options: ['A', 'B', 'C', 'D'],
  2136.           revealed: false,
  2137.           answerResult: null,
  2138.           item: doublePointsItem,
  2139.           explanation: 'Explanation',
  2140.         } as GameQuestion,
  2141.         selectedAnswer: 'A',
  2142.         showItemAfterAnswer: true,
  2143.         gameQuestions: [],
  2144.       };
  2145.     });
  2146.  
  2147.     test('Double Points should award 2x points when correct', () => {
  2148.       const newState = teamTriviaReducer(state, {
  2149.         type: 'CHECK_ANSWER',
  2150.       });
  2151.  
  2152.       expect(newState.isAnswerCorrect).toBe(true);
  2153.       expect(newState.teams[0]!.score).toBe(BASE_POINTS * 2);
  2154.       expect(newState.gameState).toBe('answer_view');
  2155.     });
  2156.  
  2157.     test('Double Points should award no points when incorrect', () => {
  2158.       state.selectedAnswer = 'B';
  2159.  
  2160.       const newState = teamTriviaReducer(state, {
  2161.         type: 'CHECK_ANSWER',
  2162.       });
  2163.  
  2164.       expect(newState.isAnswerCorrect).toBe(false);
  2165.       expect(newState.teams[0]!.score).toBe(0);
  2166.       expect(newState.gameState).toBe('answer_view');
  2167.     });
  2168.   });
  2169.  
  2170.   describe('Power-Up Effects - Point Steal', () => {
  2171.     beforeEach(() => {
  2172.       state = {
  2173.         ...state,
  2174.         gameState: 'question_view',
  2175.         teams: [
  2176.           { id: 0, name: 'Team 1', score: 0, color: 'blue' },
  2177.           { id: 1, name: 'Team 2', score: 20, color: 'red' },
  2178.         ],
  2179.         currentTeamIndex: 0,
  2180.         currentQuestion: {
  2181.           text: 'Question',
  2182.           correct_answer: 'A',
  2183.           options: ['A', 'B', 'C', 'D'],
  2184.           revealed: false,
  2185.           answerResult: null,
  2186.           item: pointStealItem,
  2187.           explanation: 'Explanation',
  2188.         } as GameQuestion,
  2189.         selectedAnswer: 'A',
  2190.         showItemAfterAnswer: true,
  2191.         gameQuestions: [],
  2192.       };
  2193.     });
  2194.  
  2195.     test('Point Steal should trigger team selection when correct', () => {
  2196.       const newState = teamTriviaReducer(state, {
  2197.         type: 'CHECK_ANSWER',
  2198.       });
  2199.  
  2200.       expect(newState.isAnswerCorrect).toBe(true);
  2201.       expect(newState.teams[0]!.score).toBe(BASE_POINTS); // Base points awarded
  2202.       expect(newState.gameState).toBe('team_selection');
  2203.       expect(newState.stealPointsAmount).toBe(5);
  2204.     });
  2205.  
  2206.     test('Point Steal should transfer points between teams', () => {
  2207.       state.stealPointsAmount = 5;
  2208.  
  2209.       const newState = teamTriviaReducer(state, {
  2210.         type: 'SELECT_TEAM_FOR_STEAL',
  2211.         payload: 1,
  2212.       });
  2213.  
  2214.       expect(newState.teams[0]!.score).toBe(5); // Current team gains 5
  2215.       expect(newState.teams[1]!.score).toBe(15); // Target team loses 5
  2216.       expect(newState.gameState).toBe('playing');
  2217.     });
  2218.  
  2219.     test('Point Steal should not allow stealing from self', () => {
  2220.       state.stealPointsAmount = 5;
  2221.  
  2222.       const newState = teamTriviaReducer(state, {
  2223.         type: 'SELECT_TEAM_FOR_STEAL',
  2224.         payload: 0, // Same as current team
  2225.       });
  2226.  
  2227.       // State should not change
  2228.       expect(newState).toEqual(state);
  2229.     });
  2230.  
  2231.     test('Point Steal should not reduce target team below 0', () => {
  2232.       state.teams[1]!.score = 3; // Less than steal amount
  2233.       state.stealPointsAmount = 5;
  2234.  
  2235.       const newState = teamTriviaReducer(state, {
  2236.         type: 'SELECT_TEAM_FOR_STEAL',
  2237.         payload: 1,
  2238.       });
  2239.  
  2240.       expect(newState.teams[0]!.score).toBe(5);
  2241.       expect(newState.teams[1]!.score).toBe(0); // Should be 0, not negative
  2242.     });
  2243.   });
  2244.  
  2245.   describe('Power-Up Effects - Swap Points', () => {
  2246.     test('SWAP_POINTS should exchange scores between teams', () => {
  2247.       state = {
  2248.         ...state,
  2249.         teams: [
  2250.           { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  2251.           { id: 1, name: 'Team 2', score: 30, color: 'red' },
  2252.         ],
  2253.       };
  2254.  
  2255.       const newState = teamTriviaReducer(state, {
  2256.         type: 'SWAP_POINTS',
  2257.         payload: { teamIndex1: 0, teamIndex2: 1 },
  2258.       });
  2259.  
  2260.       expect(newState.teams[0]!.score).toBe(30);
  2261.       expect(newState.teams[1]!.score).toBe(10);
  2262.     });
  2263.  
  2264.     test('SWAP_POINTS should not swap with self', () => {
  2265.       state = {
  2266.         ...state,
  2267.         teams: [
  2268.           { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  2269.         ],
  2270.       };
  2271.  
  2272.       const newState = teamTriviaReducer(state, {
  2273.         type: 'SWAP_POINTS',
  2274.         payload: { teamIndex1: 0, teamIndex2: 0 },
  2275.       });
  2276.  
  2277.       expect(newState).toEqual(state);
  2278.     });
  2279.   });
  2280.  
  2281.   describe('Power-Up Effects - Give Points', () => {
  2282.     beforeEach(() => {
  2283.       state = {
  2284.         ...state,
  2285.         gameState: 'question_view',
  2286.         teams: [
  2287.           { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  2288.           { id: 1, name: 'Team 2', score: 20, color: 'red' },
  2289.         ],
  2290.         currentTeamIndex: 0,
  2291.         currentQuestion: {
  2292.           text: 'Question',
  2293.           correct_answer: 'A',
  2294.           options: ['A', 'B', 'C', 'D'],
  2295.           revealed: false,
  2296.           answerResult: null,
  2297.           item: givePointsItem,
  2298.           explanation: 'Explanation',
  2299.         } as GameQuestion,
  2300.         selectedAnswer: 'A',
  2301.         showItemAfterAnswer: true,
  2302.         gameQuestions: [],
  2303.       };
  2304.     });
  2305.  
  2306.     test('Give Points should trigger team selection regardless of answer', () => {
  2307.       const correctState = teamTriviaReducer(state, {
  2308.         type: 'CHECK_ANSWER',
  2309.       });
  2310.  
  2311.       expect(correctState.isAnswerCorrect).toBe(true);
  2312.       expect(correctState.teams[0]!.score).toBe(20); // Base points awarded
  2313.       expect(correctState.gameState).toBe('team_selection');
  2314.  
  2315.       // Test with incorrect answer
  2316.       state.selectedAnswer = 'B';
  2317.       const incorrectState = teamTriviaReducer(state, {
  2318.         type: 'CHECK_ANSWER',
  2319.       });
  2320.  
  2321.       expect(incorrectState.isAnswerCorrect).toBe(false);
  2322.       expect(incorrectState.teams[0]!.score).toBe(10); // No base points
  2323.       expect(incorrectState.gameState).toBe('team_selection');
  2324.     });
  2325.   });
  2326.  
  2327.   describe('Manual Scoring', () => {
  2328.     beforeEach(() => {
  2329.       state = {
  2330.         ...state,
  2331.         gameState: 'answer_view',
  2332.         teams: [
  2333.           { id: 0, name: 'Team 1', score: 0, color: 'blue' },
  2334.         ],
  2335.         currentTeamIndex: 0,
  2336.         currentQuestion: {
  2337.           text: 'Question',
  2338.           correct_answer: 'A',
  2339.           options: ['A', 'B', 'C', 'D'],
  2340.           revealed: false,
  2341.           answerResult: null,
  2342.           item: null,
  2343.           explanation: 'Explanation',
  2344.         } as GameQuestion,
  2345.         gameQuestions: [],
  2346.       };
  2347.     });
  2348.  
  2349.     test('MANUAL_SCORE should award points when marked correct', () => {
  2350.       const newState = teamTriviaReducer(state, {
  2351.         type: 'MANUAL_SCORE',
  2352.         payload: true,
  2353.       });
  2354.  
  2355.       expect(newState.isAnswerCorrect).toBe(true);
  2356.       expect(newState.teams[0]!.score).toBe(BASE_POINTS);
  2357.     });
  2358.  
  2359.     test('MANUAL_SCORE should not award points when marked incorrect', () => {
  2360.       const newState = teamTriviaReducer(state, {
  2361.         type: 'MANUAL_SCORE',
  2362.         payload: false,
  2363.       });
  2364.  
  2365.       expect(newState.isAnswerCorrect).toBe(false);
  2366.       expect(newState.teams[0]!.score).toBe(0);
  2367.     });
  2368.   });
  2369.  
  2370.   describe('Game Progression', () => {
  2371.     beforeEach(() => {
  2372.       state = {
  2373.         ...state,
  2374.         gameState: 'answer_view',
  2375.         teams: [
  2376.           { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  2377.           { id: 1, name: 'Team 2', score: 20, color: 'red' },
  2378.         ],
  2379.         currentTeamIndex: 0,
  2380.         gameQuestions: [
  2381.           { revealed: true, item: null } as GameQuestion,
  2382.           { revealed: false, item: null } as GameQuestion,
  2383.         ],
  2384.       };
  2385.     });
  2386.  
  2387.     test('NEXT_TURN should advance to next team', () => {
  2388.       const newState = teamTriviaReducer(state, {
  2389.         type: 'NEXT_TURN',
  2390.       });
  2391.  
  2392.       expect(newState.currentTeamIndex).toBe(1);
  2393.       expect(newState.gameState).toBe('playing');
  2394.       expect(newState.currentQuestion).toBe(null);
  2395.       expect(newState.selectedAnswer).toBe(null);
  2396.     });
  2397.  
  2398.     test('NEXT_TURN should wrap around to first team after last', () => {
  2399.       state.currentTeamIndex = 1;
  2400.  
  2401.       const newState = teamTriviaReducer(state, {
  2402.         type: 'NEXT_TURN',
  2403.       });
  2404.  
  2405.       expect(newState.currentTeamIndex).toBe(0);
  2406.       expect(newState.gameState).toBe('playing');
  2407.     });
  2408.  
  2409.     test('NEXT_TURN should end game when all questions revealed', () => {
  2410.       state.gameQuestions[1]!.revealed = true;
  2411.  
  2412.       const newState = teamTriviaReducer(state, {
  2413.         type: 'NEXT_TURN',
  2414.       });
  2415.  
  2416.       expect(newState.gameState).toBe('game_over');
  2417.     });
  2418.  
  2419.     test('END_GAME_EARLY should mark for ending after round', () => {
  2420.       state.gameState = 'playing';
  2421.  
  2422.       const newState = teamTriviaReducer(state, {
  2423.         type: 'END_GAME_EARLY',
  2424.       });
  2425.  
  2426.       expect(newState.isEndingEarly).toBe(true);
  2427.     });
  2428.  
  2429.     test('RESET_GAME should retain settings but reset game state', () => {
  2430.       state.numTeams = 4;
  2431.       state.selectedNumQuestions = 15;
  2432.       state.showOptions = false;
  2433.       state.itemDensity = 'some';
  2434.  
  2435.       const newState = teamTriviaReducer(state, {
  2436.         type: 'RESET_GAME',
  2437.       });
  2438.  
  2439.       expect(newState.numTeams).toBe(4);
  2440.       expect(newState.selectedNumQuestions).toBe(15);
  2441.       expect(newState.showOptions).toBe(false);
  2442.       expect(newState.itemDensity).toBe('some');
  2443.       expect(newState.gameState).toBe('setup');
  2444.       expect(newState.teams).toEqual([]);
  2445.       expect(newState.gameQuestions).toEqual([]);
  2446.     });
  2447.   });
  2448.  
  2449.   describe('Direct Point Manipulation', () => {
  2450.     beforeEach(() => {
  2451.       state = {
  2452.         ...state,
  2453.         teams: [
  2454.           { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  2455.           { id: 1, name: 'Team 2', score: 20, color: 'red' },
  2456.         ],
  2457.       };
  2458.     });
  2459.  
  2460.     test('ADD_POINTS should increase team score', () => {
  2461.       const newState = teamTriviaReducer(state, {
  2462.         type: 'ADD_POINTS',
  2463.         payload: { teamIndex: 0, points: 5 },
  2464.       });
  2465.  
  2466.       expect(newState.teams[0]!.score).toBe(15);
  2467.     });
  2468.  
  2469.     test('DEDUCT_POINTS should decrease team score', () => {
  2470.       const newState = teamTriviaReducer(state, {
  2471.         type: 'DEDUCT_POINTS',
  2472.         payload: { teamIndex: 1, points: 5 },
  2473.       });
  2474.  
  2475.       expect(newState.teams[1]!.score).toBe(15);
  2476.     });
  2477.  
  2478.     test('DEDUCT_POINTS should not go below zero', () => {
  2479.       const newState = teamTriviaReducer(state, {
  2480.         type: 'DEDUCT_POINTS',
  2481.         payload: { teamIndex: 0, points: 15 },
  2482.       });
  2483.  
  2484.       expect(newState.teams[0]!.score).toBe(0);
  2485.     });
  2486.  
  2487.     test('STEAL_POINTS should transfer points correctly', () => {
  2488.       const newState = teamTriviaReducer(state, {
  2489.         type: 'STEAL_POINTS',
  2490.         payload: { fromTeamIndex: 1, toTeamIndex: 0, points: 5 },
  2491.       });
  2492.  
  2493.       expect(newState.teams[0]!.score).toBe(15);
  2494.       expect(newState.teams[1]!.score).toBe(15);
  2495.     });
  2496.  
  2497.     test('STEAL_POINTS should handle insufficient points', () => {
  2498.       const newState = teamTriviaReducer(state, {
  2499.         type: 'STEAL_POINTS',
  2500.         payload: { fromTeamIndex: 0, toTeamIndex: 1, points: 15 },
  2501.       });
  2502.  
  2503.       expect(newState.teams[0]!.score).toBe(0);
  2504.       expect(newState.teams[1]!.score).toBe(30);
  2505.     });
  2506.   });
  2507.  
  2508.   describe('Edge Cases and Error Handling', () => {
  2509.     test('Should handle invalid team indices gracefully', () => {
  2510.       state.teams = [
  2511.         { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  2512.       ];
  2513.  
  2514.       let newState = teamTriviaReducer(state, {
  2515.         type: 'ADD_POINTS',
  2516.         payload: { teamIndex: 5, points: 10 },
  2517.       });
  2518.       expect(newState).toEqual(state);
  2519.  
  2520.       newState = teamTriviaReducer(state, {
  2521.         type: 'DEDUCT_POINTS',
  2522.         payload: { teamIndex: -1, points: 5 },
  2523.       });
  2524.       expect(newState).toEqual(state);
  2525.     });
  2526.  
  2527.     test('Should handle selecting already revealed questions', () => {
  2528.       state.gameQuestions = [
  2529.         { revealed: true, item: null } as GameQuestion,
  2530.       ];
  2531.  
  2532.       const newState = teamTriviaReducer(state, {
  2533.         type: 'SELECT_QUESTION',
  2534.         payload: 0,
  2535.       });
  2536.  
  2537.       expect(newState).toEqual(state);
  2538.     });
  2539.  
  2540.     test('Should handle CHECK_ANSWER with no current question', () => {
  2541.       state.gameState = 'question_view';
  2542.       state.currentQuestion = null;
  2543.  
  2544.       const newState = teamTriviaReducer(state, {
  2545.         type: 'CHECK_ANSWER',
  2546.       });
  2547.  
  2548.       expect(newState).toEqual(state);
  2549.     });
  2550.  
  2551.     test('Should handle CONTINUE_AFTER_ITEM action', () => {
  2552.       state.gameState = 'item_view';
  2553.  
  2554.       const newState = teamTriviaReducer(state, {
  2555.         type: 'CONTINUE_AFTER_ITEM',
  2556.       });
  2557.  
  2558.       expect(newState.gameState).toBe('question_view');
  2559.     });
  2560.  
  2561.     test('Should handle SET_CURRENT_ITEM action', () => {
  2562.       const newState = teamTriviaReducer(state, {
  2563.         type: 'SET_CURRENT_ITEM',
  2564.         payload: doublePointsItem,
  2565.       });
  2566.  
  2567.       expect(newState.currentItemForReveal).toBe(doublePointsItem);
  2568.     });
  2569.   });
  2570.  
  2571.   describe('Complex Scenarios', () => {
  2572.     test('Should handle complete game flow with items', () => {
  2573.       // Initialize game
  2574.       state.numTeams = 2;
  2575.       state.selectedNumQuestions = 2;
  2576.      
  2577.       let currentState = teamTriviaReducer(state, {
  2578.         type: 'INITIALIZE_GAME',
  2579.         payload: {
  2580.           questions: [
  2581.             {
  2582.               text: 'Q1',
  2583.               correct_answer: 'A',
  2584.               options: ['A', 'B', 'C', 'D'],
  2585.               explanation: 'E1',
  2586.             },
  2587.             {
  2588.               text: 'Q2',
  2589.               correct_answer: 'B',
  2590.               options: ['A', 'B', 'C', 'D'],
  2591.               explanation: 'E2',
  2592.             },
  2593.           ],
  2594.           itemDensity: 'a-lot', // Use 'a-lot' to guarantee items are assigned
  2595.         },
  2596.       });
  2597.  
  2598.       expect(currentState.gameState).toBe('playing');
  2599.      
  2600.       // Verify that an item was assigned to at least one question
  2601.       const hasItem = currentState.gameQuestions.some(q => q.item !== null);
  2602.       expect(hasItem).toBe(true);
  2603.      
  2604.       // If first question has an item, test the item flow
  2605.       const firstQuestionHasItem = currentState.gameQuestions[0]!.item !== null;
  2606.      
  2607.       // Team 1 selects question
  2608.       currentState = teamTriviaReducer(currentState, {
  2609.         type: 'SELECT_QUESTION',
  2610.         payload: 0,
  2611.       });
  2612.  
  2613.       if (firstQuestionHasItem) {
  2614.         expect(currentState.gameState).toBe('item_view');
  2615.       } else {
  2616.         expect(currentState.gameState).toBe('question_view');
  2617.       }
  2618.  
  2619.       // Continue after item if there was one
  2620.       if (firstQuestionHasItem) {
  2621.         currentState = teamTriviaReducer(currentState, {
  2622.           type: 'CONTINUE_AFTER_ITEM',
  2623.         });
  2624.         expect(currentState.gameState).toBe('question_view');
  2625.         // Verify that showItemAfterAnswer is set correctly based on item category
  2626.         if (currentState.currentQuestion?.item) {
  2627.           const itemCategory = currentState.currentQuestion.item.category;
  2628.           const shouldShowAfter = itemCategory === 'yellow' || itemCategory === 'purple' || itemCategory === 'orange';
  2629.           expect(currentState.showItemAfterAnswer).toBe(shouldShowAfter);
  2630.         }
  2631.       }
  2632.  
  2633.       // Select the correct answer for this question
  2634.       const correctAnswer = currentState.currentQuestion!.correct_answer;
  2635.       currentState = teamTriviaReducer(currentState, {
  2636.         type: 'SELECT_ANSWER',
  2637.         payload: correctAnswer,
  2638.       });
  2639.  
  2640.       // Check answer - this should process any item effects if present
  2641.       currentState = teamTriviaReducer(currentState, {
  2642.         type: 'CHECK_ANSWER',
  2643.       });
  2644.  
  2645.       // Note: In this complex flow test, we're testing the overall flow
  2646.       // Specific item logic is tested separately in dedicated test cases
  2647.       // So we'll just verify the base behavior here
  2648.       expect(currentState.isAnswerCorrect).toBe(true);
  2649.      
  2650.       // Some items trigger team selection (purple/orange categories)
  2651.       // Others go directly to answer view
  2652.       const validStates = ['answer_view', 'team_selection'];
  2653.       expect(validStates).toContain(currentState.gameState);
  2654.      
  2655.       // If we're in team selection, complete that flow
  2656.       if (currentState.gameState === 'team_selection') {
  2657.         // For testing, just select team 1 (if current team is 0)
  2658.         const targetTeam = currentState.currentTeamIndex === 0 ? 1 : 0;
  2659.         currentState = teamTriviaReducer(currentState, {
  2660.           type: 'SELECT_TEAM_FOR_STEAL',
  2661.           payload: targetTeam,
  2662.         });
  2663.         // After team selection, we should be back in playing state
  2664.         expect(currentState.gameState).toBe('playing');
  2665.       } else if (currentState.gameState === 'answer_view') {
  2666.         // We're in answer view, ready for next turn
  2667.         expect(currentState.teams[0]!.score).toBeGreaterThan(0);
  2668.       }
  2669.      
  2670.       // For this test flow, always advance to next turn to test the complete game cycle
  2671.       // Note: In real game, after team selection the same team continues, but for testing we move on
  2672.       if (currentState.gameState === 'answer_view') {
  2673.         currentState = teamTriviaReducer(currentState, {
  2674.           type: 'NEXT_TURN',
  2675.         });
  2676.         expect(currentState.currentTeamIndex).toBe(1);
  2677.         expect(currentState.gameState).toBe('playing');
  2678.       } else {
  2679.         // We're already in playing state after team selection
  2680.         // The flow test ends here - team 0 completed their action
  2681.         expect(currentState.gameState).toBe('playing');
  2682.         expect(currentState.currentTeamIndex).toBe(0); // Still team 0's turn
  2683.       }
  2684.     });
  2685.  
  2686.     test('Should handle all questions revealed triggering game over', () => {
  2687.       state = {
  2688.         ...state,
  2689.         gameState: 'answer_view',
  2690.         teams: [
  2691.           { id: 0, name: 'Team 1', score: 10, color: 'blue' },
  2692.         ],
  2693.         currentTeamIndex: 0,
  2694.         gameQuestions: [
  2695.           { revealed: true, item: null } as GameQuestion,
  2696.           { revealed: true, item: null } as GameQuestion,
  2697.         ],
  2698.       };
  2699.  
  2700.       const newState = teamTriviaReducer(state, {
  2701.         type: 'NEXT_TURN',
  2702.       });
  2703.  
  2704.       expect(newState.gameState).toBe('game_over');
  2705.     });
  2706.   });
  2707. });
  2708. </file>
  2709.  
  2710. </files>
  2711.  
Advertisement
Add Comment
Please, Sign In to add comment