dramaling-vocab-learning/frontend/lib/services/review/__tests__/reviewService.test.ts

152 lines
4.9 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, beforeEach, vi } from 'vitest'
import { ReviewService } from '../reviewService'
describe('ReviewService', () => {
describe('transformToExtendedFlashcard', () => {
it('應該正確轉換基礎 Flashcard 為 ExtendedFlashcard', () => {
const basicFlashcard = {
id: 'test-1',
word: 'hello',
translation: '你好',
definition: 'greeting',
partOfSpeech: 'interjection',
pronunciation: '/həˈloʊ/',
example: 'Hello world',
masteryLevel: 1,
timesReviewed: 3,
isFavorite: false,
nextReviewDate: '2025-10-03T00:00:00Z',
cefr: 'A1',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
exampleImages: [],
hasExampleImage: false
}
const extended = ReviewService.transformToExtendedFlashcard(basicFlashcard)
// 基礎欄位保持不變
expect(extended.id).toBe('test-1')
expect(extended.word).toBe('hello')
expect(extended.nextReviewDate).toBe('2025-10-03T00:00:00Z')
// 新增的擴展欄位有預設值
expect(extended.synonyms).toEqual([])
expect(extended.reviewCount).toBe(3) // 來自 timesReviewed
expect(extended.successRate).toBe(0)
expect(extended.currentInterval).toBe(0)
expect(extended.isOverdue).toBe(false)
})
it('應該處理缺少可選欄位的情況', () => {
const minimalFlashcard = {
id: 'minimal',
word: 'test',
translation: '測試',
definition: 'test definition',
partOfSpeech: 'noun',
pronunciation: '/test/',
example: 'This is a test',
masteryLevel: 0,
timesReviewed: 0,
isFavorite: false,
nextReviewDate: '2025-10-03T00:00:00Z',
cefr: 'A1',
createdAt: '2024-01-01T00:00:00Z',
exampleImages: [],
hasExampleImage: false
// 缺少 updatedAt, primaryImageUrl 等
}
const extended = ReviewService.transformToExtendedFlashcard(minimalFlashcard)
expect(extended.synonyms).toEqual([])
expect(extended.exampleImage).toBeUndefined()
expect(extended.reviewCount).toBe(0)
expect(extended.lastReviewDate).toBeUndefined()
})
it('應該為缺少 nextReviewDate 的數據提供預設值', () => {
const flashcardWithoutDate = {
id: 'no-date',
word: 'test',
// nextReviewDate 缺失
masteryLevel: 0,
timesReviewed: 0
}
const extended = ReviewService.transformToExtendedFlashcard(flashcardWithoutDate)
expect(extended.nextReviewDate).toBeDefined()
expect(new Date(extended.nextReviewDate!)).toBeInstanceOf(Date)
})
})
describe('calculateStats', () => {
it('應該正確計算學習統計', () => {
const testItems = [
{ id: '1', isCompleted: true },
{ id: '2', isCompleted: false },
{ id: '3', isCompleted: true },
{ id: '4', isCompleted: false }
]
const score = { correct: 3, total: 4 }
const stats = ReviewService.calculateStats(testItems as any, score)
expect(stats.completed).toBe(2)
expect(stats.total).toBe(4)
expect(stats.remaining).toBe(2)
expect(stats.progressPercentage).toBe(50) // 2/4 = 50%
expect(stats.accuracyPercentage).toBe(75) // 3/4 = 75%
expect(stats.estimatedTimeRemaining).toBe(60) // 2 * 30秒
})
it('應該處理空數據的情況', () => {
const testItems: any[] = []
const score = { correct: 0, total: 0 }
const stats = ReviewService.calculateStats(testItems, score)
expect(stats.completed).toBe(0)
expect(stats.total).toBe(0)
expect(stats.remaining).toBe(0)
expect(stats.progressPercentage).toBe(0)
expect(stats.accuracyPercentage).toBe(0)
expect(stats.estimatedTimeRemaining).toBe(0)
})
})
describe('validateSession', () => {
it('應該驗證有效的學習會話', () => {
const cards = [
{ id: 'card1', word: 'hello' },
{ id: 'card2', word: 'world' }
]
const testItems = [
{ cardId: 'card1', id: 'test1' },
{ cardId: 'card2', id: 'test2' }
]
const validation = ReviewService.validateSession(cards as any, testItems as any)
expect(validation.isValid).toBe(true)
expect(validation.errors).toEqual([])
})
it('應該檢測無效的學習會話', () => {
const cards: any[] = []
const testItems = [
{ cardId: 'non-existent', id: 'test1' }
]
const validation = ReviewService.validateSession(cards, testItems as any)
expect(validation.isValid).toBe(false)
expect(validation.errors).toContain('沒有可用的詞卡')
expect(validation.errors).toContain('測驗項目引用了不存在的詞卡: non-existent')
})
})
})