import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { renderHook, act } from '@testing-library/react' import { BaseTestComponent, useTestAnswer } from '../../shared/BaseTestComponent' // Mock 依賴組件 vi.mock('@/components/review/shared', () => ({ ErrorReportButton: ({ onClick }: any) => ( ), TestHeader: ({ title, cefr }: any) => (

{title}

CEFR: {cefr}
) })) describe('BaseTestComponent', () => { const mockCardData = { id: 'test-1', word: 'hello', definition: 'a greeting', example: 'Hello world', translation: '你好', pronunciation: '/həˈloʊ/', cefr: 'A1', synonyms: [], exampleImage: undefined } const mockOnReportError = vi.fn() beforeEach(() => { vi.clearAllMocks() }) describe('基礎渲染', () => { it('應該正確渲染測驗標題和基本結構', () => { render(
測試內容
) expect(screen.getByTestId('test-header')).toBeInTheDocument() expect(screen.getByText('測試標題')).toBeInTheDocument() expect(screen.getByText('CEFR: A1')).toBeInTheDocument() expect(screen.getByTestId('test-content')).toBeInTheDocument() }) it('應該顯示錯誤回報按鈕', () => { render(
內容
) expect(screen.getByTestId('error-report-button')).toBeInTheDocument() }) it('應該在有說明時顯示說明文字', () => { render(
內容
) expect(screen.getByText('這是測試說明')).toBeInTheDocument() }) }) describe('結果顯示', () => { it('應該在 showResult 為 true 時顯示結果內容', () => { render( 測試結果} onReportError={mockOnReportError} >
內容
) expect(screen.getByTestId('result')).toBeInTheDocument() }) it('應該在 showResult 為 false 時隱藏結果內容', () => { render( 測試結果} onReportError={mockOnReportError} >
內容
) expect(screen.queryByTestId('result')).not.toBeInTheDocument() }) }) describe('錯誤回報功能', () => { it('應該在點擊錯誤回報按鈕時調用 onReportError', async () => { const user = userEvent.setup() render(
內容
) const errorButton = screen.getByTestId('error-report-button') await user.click(errorButton) expect(mockOnReportError).toHaveBeenCalledTimes(1) }) }) describe('自定義樣式', () => { it('應該應用自定義 className', () => { const { container } = render(
內容
) expect(container.firstChild).toHaveClass('custom-test-class') }) }) }) describe('useTestAnswer Hook', () => { const mockOnAnswer = vi.fn() beforeEach(() => { vi.clearAllMocks() }) describe('初始狀態', () => { it('應該有正確的初始值', () => { const { result } = renderHook(() => useTestAnswer(mockOnAnswer)) expect(result.current.selectedAnswer).toBeNull() expect(result.current.showResult).toBe(false) }) }) describe('答題功能', () => { it('應該在 handleAnswer 時更新狀態並調用回調', () => { const { result } = renderHook(() => useTestAnswer(mockOnAnswer)) act(() => { result.current.handleAnswer('test answer') }) expect(result.current.selectedAnswer).toBe('test answer') expect(result.current.showResult).toBe(true) expect(mockOnAnswer).toHaveBeenCalledWith('test answer') }) it('應該防止重複提交', async () => { const { result } = renderHook(() => useTestAnswer(mockOnAnswer)) // 第一次提交 act(() => { result.current.handleAnswer('first answer') }) expect(result.current.showResult).toBe(true) expect(mockOnAnswer).toHaveBeenCalledTimes(1) // 第二次提交應該被阻止 act(() => { result.current.handleAnswer('second answer') }) // onAnswer 不應該被再次調用 expect(mockOnAnswer).toHaveBeenCalledTimes(1) expect(mockOnAnswer).toHaveBeenLastCalledWith('first answer') }) }) describe('重置功能', () => { it('應該正確重置所有狀態', () => { const { result } = renderHook(() => useTestAnswer(mockOnAnswer)) // 先設置一些狀態 act(() => { result.current.handleAnswer('test answer') }) expect(result.current.selectedAnswer).toBe('test answer') expect(result.current.showResult).toBe(true) // 重置 act(() => { result.current.resetAnswer() }) expect(result.current.selectedAnswer).toBeNull() expect(result.current.showResult).toBe(false) }) }) describe('邊界情況', () => { it('應該處理空字符串答案', () => { const { result } = renderHook(() => useTestAnswer(mockOnAnswer)) act(() => { result.current.handleAnswer('') }) expect(result.current.selectedAnswer).toBe('') expect(mockOnAnswer).toHaveBeenCalledWith('') }) it('應該處理多次重置', () => { const { result } = renderHook(() => useTestAnswer(mockOnAnswer)) act(() => { result.current.resetAnswer() result.current.resetAnswer() result.current.resetAnswer() }) expect(result.current.selectedAnswer).toBeNull() expect(result.current.showResult).toBe(false) }) }) describe('Hook 穩定性', () => { it('應該在依賴未變時保持函數引用穩定', () => { const { result, rerender } = renderHook(() => useTestAnswer(mockOnAnswer)) const firstHandleAnswer = result.current.handleAnswer const firstResetAnswer = result.current.resetAnswer rerender() expect(result.current.handleAnswer).toBe(firstHandleAnswer) expect(result.current.resetAnswer).toBe(firstResetAnswer) }) }) })