import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Lobby } from '../../components/Lobby'; vi.mock('react-hot-toast', () => ({ default: { success: vi.fn(), error: vi.fn(), }, })); vi.mock('framer-motion', () => ({ motion: { div: ({ children, ...props }: React.PropsWithChildren>) =>
{children}
, }, AnimatePresence: ({ children }: React.PropsWithChildren) => <>{children}, })); vi.mock('qrcode.react', () => ({ QRCodeSVG: ({ value, size }: { value: string; size: number }) => ( ), })); describe('Lobby', () => { const defaultProps = { quizTitle: 'Test Quiz', players: [], gamePin: 'ABC123', role: 'HOST' as const, onStart: vi.fn(), onEndGame: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); Object.defineProperty(window, 'location', { value: { origin: 'http://localhost:5173' }, writable: true, }); }); describe('copy join link', () => { it('shows copy link button for host', () => { render(); expect(screen.getByTitle('Copy join link')).toBeInTheDocument(); }); it('does not show copy link button for client', () => { render(); expect(screen.queryByTitle('Copy join link')).not.toBeInTheDocument(); }); it('copies join URL to clipboard when clicked', async () => { const user = userEvent.setup(); render(); const copyButton = screen.getByTitle('Copy join link'); expect(copyButton.querySelector('.lucide-link')).toBeInTheDocument(); await user.click(copyButton); await waitFor(() => { expect(copyButton.querySelector('.lucide-check')).toBeInTheDocument(); }); }); it('shows success toast when link is copied', async () => { const toast = await import('react-hot-toast'); const user = userEvent.setup(); render(); await user.click(screen.getByTitle('Copy join link')); await waitFor(() => { expect(toast.default.success).toHaveBeenCalledWith('Join link copied!'); }); }); it('shows checkmark after copying link', async () => { const user = userEvent.setup(); render(); const copyButton = screen.getByTitle('Copy join link'); await user.click(copyButton); await waitFor(() => { expect(copyButton.querySelector('.lucide-check')).toBeInTheDocument(); }); }); }); describe('display', () => { it('displays game PIN', () => { render(); expect(screen.getByText('ABC123')).toBeInTheDocument(); }); it('displays quiz title on larger screens', () => { render(); expect(screen.getByText('Test Quiz')).toBeInTheDocument(); }); it('displays player count', () => { const players = [ { id: '1', name: 'Player 1', score: 0, avatarSeed: 0.1 }, { id: '2', name: 'Player 2', score: 0, avatarSeed: 0.2 }, ]; render(); expect(screen.getByText('2')).toBeInTheDocument(); }); it('shows waiting message when no players have joined', () => { render(); expect(screen.getByText('Waiting for players to join...')).toBeInTheDocument(); }); it('displays player names when players join', () => { const players = [ { id: '1', name: 'Alice', score: 0, avatarSeed: 0.1 }, { id: '2', name: 'Bob', score: 0, avatarSeed: 0.2 }, ]; render(); expect(screen.getByText('Alice')).toBeInTheDocument(); expect(screen.getByText('Bob')).toBeInTheDocument(); }); }); describe('host controls', () => { it('shows Start button for host', () => { render(); expect(screen.getByRole('button', { name: 'Start' })).toBeInTheDocument(); }); it('disables Start button when no players', () => { render(); expect(screen.getByRole('button', { name: 'Start' })).toBeDisabled(); }); it('enables Start button when players joined', () => { const players = [{ id: '1', name: 'Player 1', score: 0, avatarSeed: 0.1 }]; render(); expect(screen.getByRole('button', { name: 'Start' })).not.toBeDisabled(); }); it('calls onStart when Start clicked', async () => { const user = userEvent.setup(); const players = [{ id: '1', name: 'Player 1', score: 0, avatarSeed: 0.1 }]; render(); await user.click(screen.getByRole('button', { name: 'Start' })); expect(defaultProps.onStart).toHaveBeenCalled(); }); it('shows End Game button when onEndGame provided', () => { render(); expect(screen.getByText('End Game')).toBeInTheDocument(); }); it('calls onEndGame when End Game clicked', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByText('End Game')); expect(defaultProps.onEndGame).toHaveBeenCalled(); }); }); describe('client view', () => { it('shows waiting message for client', () => { render(); expect(screen.getByText('Waiting for the host to start...')).toBeInTheDocument(); }); it('shows player name when currentPlayerId matches', () => { const players = [ { id: 'player-1', name: 'My Name', score: 0, avatarSeed: 0.5 }, ]; render( ); expect(screen.getByText('My Name')).toBeInTheDocument(); }); it('does not show Start button for client', () => { render(); expect(screen.queryByRole('button', { name: 'Start' })).not.toBeInTheDocument(); }); }); describe('QR code', () => { it('shows QR code for host', () => { render(); const qrCode = screen.getByTestId('qr-code'); expect(qrCode).toBeInTheDocument(); expect(qrCode).toHaveAttribute('data-value', 'http://localhost:5173/play/ABC123'); }); it('does not show QR code for client', () => { render(); expect(screen.queryByTestId('qr-code')).not.toBeInTheDocument(); }); it('does not show QR code when gamePin is null', () => { render(); expect(screen.queryByTestId('qr-code')).not.toBeInTheDocument(); }); it('generates correct QR code URL with game PIN', () => { render(); const qrCode = screen.getByTestId('qr-code'); expect(qrCode).toHaveAttribute('data-value', 'http://localhost:5173/play/XYZ789'); }); }); describe('host participates mode', () => { it('shows host in player list when hostParticipates is true', () => { const players = [ { id: 'host', name: 'Host Player', score: 0, avatarSeed: 0.99 }, ]; render(); expect(screen.getByText('Host Player')).toBeInTheDocument(); expect(screen.getByText('HOST')).toBeInTheDocument(); }); it('does not show waiting message when host participates and no other players', () => { const players = [ { id: 'host', name: 'Host Player', score: 0, avatarSeed: 0.99 }, ]; render(); expect(screen.queryByText('Waiting for players to join...')).not.toBeInTheDocument(); }); }); });