import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { AutodraftSettings } from '~/components/AutodraftSettings'; vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() }, })); global.fetch = vi.fn(); describe('AutodraftSettings Component', () => { const defaultProps = { seasonId: 'season-123', teamId: 'team-456', isEnabled: false, mode: 'next_pick' as const, queueOnly: false, isMyTurn: false, onUpdate: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); (global.fetch as any).mockResolvedValue({ ok: true, json: async () => ({ success: true }), }); }); // ─── Rendering ──────────────────────────────────────────────────────────── describe('Rendering', () => { it('renders all four option buttons', () => { render(); expect(screen.getByRole('button', { name: 'Off' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Next in Queue' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'All in Queue' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'All Picks' })).toBeInTheDocument(); }); it('does not render a queue-only toggle switch', () => { render(); expect(screen.queryByRole('switch')).not.toBeInTheDocument(); }); it('shows "You\'re on the clock!" when isMyTurn=true', () => { render(); expect(screen.getByText(/You're on the clock!/i)).toBeInTheDocument(); }); it('marks Off as active (muted border) when isEnabled=false', () => { render(); expect(screen.getByRole('button', { name: 'Off' }).className).toContain( 'border-l-muted-foreground' ); }); it('marks Next in Queue as active when isEnabled=true, mode=next_pick', () => { render(); expect(screen.getByRole('button', { name: 'Next in Queue' }).className).toContain('bg-electric'); }); it('marks All in Queue as active when isEnabled=true, mode=while_on, queueOnly=true', () => { render(); expect(screen.getByRole('button', { name: 'All in Queue' }).className).toContain('bg-electric'); }); it('marks All Picks as active when isEnabled=true, mode=while_on, queueOnly=false', () => { render(); expect(screen.getByRole('button', { name: 'All Picks' }).className).toContain('bg-electric'); }); }); // ─── Interaction ────────────────────────────────────────────────────────── describe('Button interaction', () => { it('switches to Next in Queue and calls onUpdate with correct args', async () => { const onUpdate = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( '/api/autodraft/update', expect.objectContaining({ method: 'POST' }) ); }); await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'next_pick', true)); }); it('switches to All in Queue and calls onUpdate with correct args', async () => { const onUpdate = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'while_on', true)); }); it('switches to All Picks and calls onUpdate with correct args', async () => { const onUpdate = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'while_on', false)); }); it('switches to Off and calls onUpdate with isEnabled=false', async () => { const onUpdate = vi.fn(); render( ); fireEvent.click(screen.getByRole('button', { name: 'Off' })); await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(false, 'next_pick', false)); }); it('disables all buttons when isMyTurn=true', () => { render(); expect(screen.getByRole('button', { name: 'Off' })).toBeDisabled(); expect(screen.getByRole('button', { name: 'Next in Queue' })).toBeDisabled(); expect(screen.getByRole('button', { name: 'All in Queue' })).toBeDisabled(); expect(screen.getByRole('button', { name: 'All Picks' })).toBeDisabled(); }); it('does not call fetch when a button is clicked during the user\'s turn', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); expect(global.fetch).not.toHaveBeenCalled(); }); it('does not re-fire when the already-active option is clicked', async () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Off' })); expect(global.fetch).not.toHaveBeenCalled(); }); }); // ─── Optimistic UI ──────────────────────────────────────────────────────── describe('Optimistic UI', () => { it('does not disable buttons while a fetch is in flight', async () => { let resolve: (v: any) => void; (global.fetch as any).mockReturnValueOnce(new Promise((r) => { resolve = r; })); render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); // Buttons stay enabled — optimistic UI does not block interaction expect(screen.getByRole('button', { name: 'Off' })).not.toBeDisabled(); resolve!({ ok: true, json: async () => ({ success: true }) }); }); it('fires a fetch for every rapid click, aborting previous in-flight requests', async () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); fireEvent.click(screen.getByRole('button', { name: 'Off' })); await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(3)); }); }); // ─── Error handling ─────────────────────────────────────────────────────── describe('Error handling', () => { it('reverts to the previous state on a non-ok API response', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: false }); render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => { expect(screen.getByRole('button', { name: 'Off' }).className).toContain( 'border-l-muted-foreground' ); }); }); it('reverts to the previous state on a network error', async () => { (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => { expect(screen.getByRole('button', { name: 'Off' }).className).toContain( 'border-l-muted-foreground' ); }); }); }); // ─── API payload ────────────────────────────────────────────────────────── describe('API payload', () => { it('posts correct formData for Next in Queue (next_pick, queueOnly=true)', async () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); const body = (global.fetch as any).mock.calls[0][1].body as FormData; expect(body.get('seasonId')).toBe('season-123'); expect(body.get('teamId')).toBe('team-456'); expect(body.get('isEnabled')).toBe('true'); expect(body.get('mode')).toBe('next_pick'); expect(body.get('queueOnly')).toBe('true'); }); it('posts correct formData for All in Queue (while_on, queueOnly=true)', async () => { render(); fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); const body = (global.fetch as any).mock.calls[0][1].body as FormData; expect(body.get('isEnabled')).toBe('true'); expect(body.get('mode')).toBe('while_on'); expect(body.get('queueOnly')).toBe('true'); }); it('posts correct formData for All Picks (while_on, queueOnly=false)', async () => { render(); fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); const body = (global.fetch as any).mock.calls[0][1].body as FormData; expect(body.get('isEnabled')).toBe('true'); expect(body.get('mode')).toBe('while_on'); expect(body.get('queueOnly')).toBe('false'); }); it('posts isEnabled=false for Off', async () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Off' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); const body = (global.fetch as any).mock.calls[0][1].body as FormData; expect(body.get('isEnabled')).toBe('false'); }); it('passes an AbortSignal to fetch', async () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); const options = (global.fetch as any).mock.calls[0][1]; expect(options.signal).toBeInstanceOf(AbortSignal); }); }); });