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') }) }) })