152 lines
4.9 KiB
TypeScript
152 lines
4.9 KiB
TypeScript
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')
|
||
})
|
||
})
|
||
}) |