import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SaveOptionsModal } from '../../components/SaveOptionsModal'; describe('SaveOptionsModal', () => { const defaultProps = { isOpen: true, onClose: vi.fn(), onSaveNew: vi.fn(), onOverwrite: vi.fn(), isSaving: false, }; beforeEach(() => { vi.clearAllMocks(); }); describe('rendering', () => { it('renders nothing when isOpen is false', () => { render(); expect(screen.queryByText('Save Quiz')).not.toBeInTheDocument(); }); it('renders modal when isOpen is true', () => { render(); expect(screen.getByText('Save Quiz')).toBeInTheDocument(); }); it('displays explanation text', () => { render(); expect(screen.getByText(/loaded from your library/i)).toBeInTheDocument(); }); it('shows update existing button', () => { render(); expect(screen.getByText('Update existing quiz')).toBeInTheDocument(); }); it('shows save as new button', () => { render(); expect(screen.getByText('Save as new quiz')).toBeInTheDocument(); }); it('shows close button', () => { render(); const closeButtons = screen.getAllByRole('button'); expect(closeButtons.length).toBeGreaterThanOrEqual(3); }); }); describe('interactions - happy path', () => { it('calls onOverwrite when update existing is clicked', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByText('Update existing quiz')); expect(defaultProps.onOverwrite).toHaveBeenCalledTimes(1); }); it('calls onSaveNew when save as new is clicked', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByText('Save as new quiz')); expect(defaultProps.onSaveNew).toHaveBeenCalledTimes(1); }); it('calls onClose when close button (X) is clicked', async () => { const user = userEvent.setup(); render(); const closeButton = screen.getByRole('button', { name: '' }); await user.click(closeButton); expect(defaultProps.onClose).toHaveBeenCalledTimes(1); }); it('calls onClose when backdrop is clicked', async () => { render(); const backdrop = document.querySelector('.fixed.inset-0'); expect(backdrop).toBeInTheDocument(); fireEvent.click(backdrop!); expect(defaultProps.onClose).toHaveBeenCalled(); }); it('does not close when modal content is clicked', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByText('Save Quiz')); expect(defaultProps.onClose).not.toHaveBeenCalled(); }); }); describe('interactions - unhappy path / edge cases', () => { it('disables buttons when isSaving is true', () => { render(); const updateButton = screen.getByText('Update existing quiz').closest('button'); const saveNewButton = screen.getByText('Save as new quiz').closest('button'); expect(updateButton).toBeDisabled(); expect(saveNewButton).toBeDisabled(); }); it('does not call onOverwrite when disabled and clicked', async () => { const user = userEvent.setup(); render(); const button = screen.getByText('Update existing quiz').closest('button')!; await user.click(button); expect(defaultProps.onOverwrite).not.toHaveBeenCalled(); }); it('does not call onSaveNew when disabled and clicked', async () => { const user = userEvent.setup(); render(); const button = screen.getByText('Save as new quiz').closest('button')!; await user.click(button); expect(defaultProps.onSaveNew).not.toHaveBeenCalled(); }); it('handles rapid clicks gracefully', async () => { const user = userEvent.setup(); render(); const button = screen.getByText('Update existing quiz'); await user.click(button); await user.click(button); await user.click(button); expect(defaultProps.onOverwrite).toHaveBeenCalledTimes(3); }); it('remains functional after re-opening', async () => { const user = userEvent.setup(); const { rerender } = render(); await user.click(screen.getByText('Update existing quiz')); expect(defaultProps.onOverwrite).toHaveBeenCalledTimes(1); rerender(); expect(screen.queryByText('Save Quiz')).not.toBeInTheDocument(); rerender(); await user.click(screen.getByText('Save as new quiz')); expect(defaultProps.onSaveNew).toHaveBeenCalledTimes(1); }); }); describe('accessibility', () => { it('all interactive elements are focusable', () => { render(); const buttons = screen.getAllByRole('button'); buttons.forEach(button => { expect(button).not.toHaveAttribute('tabindex', '-1'); }); }); it('buttons have descriptive text for screen readers', () => { render(); expect(screen.getByText('Overwrite the original with your changes')).toBeInTheDocument(); expect(screen.getByText('Keep the original and create a copy')).toBeInTheDocument(); }); }); describe('state transitions', () => { it('transitions from not saving to saving correctly', async () => { const { rerender } = render(); const updateButton = screen.getByText('Update existing quiz').closest('button'); expect(updateButton).not.toBeDisabled(); rerender(); expect(updateButton).toBeDisabled(); }); it('transitions from saving back to not saving', async () => { const { rerender } = render(); const updateButton = screen.getByText('Update existing quiz').closest('button'); expect(updateButton).toBeDisabled(); rerender(); expect(updateButton).not.toBeDisabled(); }); }); });