dramaling-vocab-learning/frontend/components/review/__tests__/shared/BaseTestComponent.test.tsx

274 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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) => (
<button onClick={onClick} data-testid="error-report-button">
</button>
),
TestHeader: ({ title, cefr }: any) => (
<div data-testid="test-header">
<h2>{title}</h2>
<span>CEFR: {cefr}</span>
</div>
)
}))
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(
<BaseTestComponent
cardData={mockCardData}
testTitle="測試標題"
onReportError={mockOnReportError}
>
<div data-testid="test-content"></div>
</BaseTestComponent>
)
expect(screen.getByTestId('test-header')).toBeInTheDocument()
expect(screen.getByText('測試標題')).toBeInTheDocument()
expect(screen.getByText('CEFR: A1')).toBeInTheDocument()
expect(screen.getByTestId('test-content')).toBeInTheDocument()
})
it('應該顯示錯誤回報按鈕', () => {
render(
<BaseTestComponent
cardData={mockCardData}
testTitle="測試"
onReportError={mockOnReportError}
>
<div></div>
</BaseTestComponent>
)
expect(screen.getByTestId('error-report-button')).toBeInTheDocument()
})
it('應該在有說明時顯示說明文字', () => {
render(
<BaseTestComponent
cardData={mockCardData}
testTitle="測試"
instructions="這是測試說明"
onReportError={mockOnReportError}
>
<div></div>
</BaseTestComponent>
)
expect(screen.getByText('這是測試說明')).toBeInTheDocument()
})
})
describe('結果顯示', () => {
it('應該在 showResult 為 true 時顯示結果內容', () => {
render(
<BaseTestComponent
cardData={mockCardData}
testTitle="測試"
showResult={true}
resultContent={<div data-testid="result"></div>}
onReportError={mockOnReportError}
>
<div></div>
</BaseTestComponent>
)
expect(screen.getByTestId('result')).toBeInTheDocument()
})
it('應該在 showResult 為 false 時隱藏結果內容', () => {
render(
<BaseTestComponent
cardData={mockCardData}
testTitle="測試"
showResult={false}
resultContent={<div data-testid="result"></div>}
onReportError={mockOnReportError}
>
<div></div>
</BaseTestComponent>
)
expect(screen.queryByTestId('result')).not.toBeInTheDocument()
})
})
describe('錯誤回報功能', () => {
it('應該在點擊錯誤回報按鈕時調用 onReportError', async () => {
const user = userEvent.setup()
render(
<BaseTestComponent
cardData={mockCardData}
testTitle="測試"
onReportError={mockOnReportError}
>
<div></div>
</BaseTestComponent>
)
const errorButton = screen.getByTestId('error-report-button')
await user.click(errorButton)
expect(mockOnReportError).toHaveBeenCalledTimes(1)
})
})
describe('自定義樣式', () => {
it('應該應用自定義 className', () => {
const { container } = render(
<BaseTestComponent
cardData={mockCardData}
testTitle="測試"
className="custom-test-class"
onReportError={mockOnReportError}
>
<div></div>
</BaseTestComponent>
)
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)
})
})
})