202 lines
6.3 KiB
TypeScript
202 lines
6.3 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||
import { useReviewDataStore } from '../useReviewDataStore'
|
||
import { mockDueCards } from '@/lib/mock/reviewMockData'
|
||
|
||
// Mock flashcardsService
|
||
vi.mock('@/lib/services/flashcards', () => ({
|
||
flashcardsService: {
|
||
getDueFlashcards: vi.fn()
|
||
}
|
||
}))
|
||
|
||
// Mock isTestMode
|
||
vi.mock('@/lib/mock/reviewMockData', async (importOriginal) => {
|
||
const original: any = await importOriginal()
|
||
return {
|
||
...original,
|
||
isTestMode: vi.fn(),
|
||
getMockDueCards: vi.fn(() => mockDueCards)
|
||
}
|
||
})
|
||
|
||
describe('useReviewDataStore', () => {
|
||
beforeEach(() => {
|
||
// 重置 store 到初始狀態
|
||
useReviewDataStore.getState().resetData()
|
||
vi.clearAllMocks()
|
||
})
|
||
|
||
describe('初始狀態', () => {
|
||
it('應該有正確的初始值', () => {
|
||
const state = useReviewDataStore.getState()
|
||
|
||
expect(state.dueCards).toEqual([])
|
||
expect(state.showComplete).toBe(false)
|
||
expect(state.showNoDueCards).toBe(false)
|
||
expect(state.isLoadingCards).toBe(false)
|
||
expect(state.loadingError).toBe(null)
|
||
})
|
||
})
|
||
|
||
describe('loadDueCards 測試模式', () => {
|
||
beforeEach(() => {
|
||
vi.mocked(require('@/lib/mock/reviewMockData').isTestMode).mockReturnValue(true)
|
||
})
|
||
|
||
it('應該在測試模式下載入 Mock 數據', async () => {
|
||
const store = useReviewDataStore.getState()
|
||
|
||
await store.loadDueCards()
|
||
|
||
expect(store.dueCards).toEqual(mockDueCards)
|
||
expect(store.showNoDueCards).toBe(false)
|
||
expect(store.showComplete).toBe(false)
|
||
expect(store.isLoadingCards).toBe(false)
|
||
})
|
||
|
||
it('應該正確設置載入狀態', async () => {
|
||
const store = useReviewDataStore.getState()
|
||
|
||
// 開始載入時檢查狀態
|
||
const loadPromise = store.loadDueCards()
|
||
expect(store.isLoadingCards).toBe(true)
|
||
|
||
// 等待完成
|
||
await loadPromise
|
||
expect(store.isLoadingCards).toBe(false)
|
||
})
|
||
|
||
it('應該在測試模式下不呼叫真實 API', async () => {
|
||
const { flashcardsService } = await import('@/lib/services/flashcards')
|
||
const store = useReviewDataStore.getState()
|
||
|
||
await store.loadDueCards()
|
||
|
||
expect(flashcardsService.getDueFlashcards).not.toHaveBeenCalled()
|
||
})
|
||
})
|
||
|
||
describe('loadDueCards 正常模式', () => {
|
||
beforeEach(() => {
|
||
vi.mocked(require('@/lib/mock/reviewMockData').isTestMode).mockReturnValue(false)
|
||
})
|
||
|
||
it('應該成功載入後端數據', async () => {
|
||
const { flashcardsService } = await import('@/lib/services/flashcards')
|
||
|
||
// 創建符合 Flashcard 類型的 Mock 數據
|
||
const mockFlashcard = {
|
||
id: 'mock-1',
|
||
word: 'hello',
|
||
translation: '你好',
|
||
definition: 'used as a greeting',
|
||
partOfSpeech: 'interjection',
|
||
pronunciation: '/həˈloʊ/',
|
||
example: 'Hello, how are you today?',
|
||
exampleTranslation: '你好,你今天好嗎?',
|
||
masteryLevel: 0,
|
||
timesReviewed: 0,
|
||
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,
|
||
primaryImageUrl: undefined
|
||
}
|
||
|
||
const mockApiResponse = {
|
||
success: true,
|
||
data: [mockFlashcard]
|
||
}
|
||
vi.mocked(flashcardsService.getDueFlashcards).mockResolvedValue(mockApiResponse)
|
||
|
||
const store = useReviewDataStore.getState()
|
||
await store.loadDueCards()
|
||
|
||
// 期望轉換後的 ExtendedFlashcard 格式
|
||
expect(store.dueCards).toHaveLength(1)
|
||
expect(store.dueCards[0].word).toBe('hello')
|
||
expect(store.dueCards[0].synonyms).toEqual([]) // 轉換層添加的預設值
|
||
expect(store.showNoDueCards).toBe(false)
|
||
expect(flashcardsService.getDueFlashcards).toHaveBeenCalledWith(50)
|
||
})
|
||
|
||
it('應該處理 API 錯誤', async () => {
|
||
const { flashcardsService } = await import('@/lib/services/flashcards')
|
||
vi.mocked(flashcardsService.getDueFlashcards).mockRejectedValue(new Error('API錯誤'))
|
||
|
||
const store = useReviewDataStore.getState()
|
||
await store.loadDueCards()
|
||
|
||
expect(store.dueCards).toEqual([])
|
||
expect(store.showNoDueCards).toBe(true)
|
||
expect(store.loadingError).toBe('載入詞卡失敗')
|
||
})
|
||
|
||
it('應該處理空數據回應', async () => {
|
||
const { flashcardsService } = await import('@/lib/services/flashcards')
|
||
const mockApiResponse = {
|
||
success: true,
|
||
data: []
|
||
}
|
||
vi.mocked(flashcardsService.getDueFlashcards).mockResolvedValue(mockApiResponse)
|
||
|
||
const store = useReviewDataStore.getState()
|
||
await store.loadDueCards()
|
||
|
||
expect(store.dueCards).toEqual([])
|
||
expect(store.showNoDueCards).toBe(true)
|
||
})
|
||
})
|
||
|
||
describe('工具方法', () => {
|
||
beforeEach(() => {
|
||
const store = useReviewDataStore.getState()
|
||
store.setDueCards(mockDueCards)
|
||
})
|
||
|
||
it('getDueCardsCount 應該返回正確數量', () => {
|
||
const store = useReviewDataStore.getState()
|
||
|
||
expect(store.getDueCardsCount()).toBe(3)
|
||
})
|
||
|
||
it('findCardById 應該找到正確的詞卡', () => {
|
||
const store = useReviewDataStore.getState()
|
||
|
||
const foundCard = store.findCardById('mock-1')
|
||
expect(foundCard).toBeDefined()
|
||
expect(foundCard?.word).toBe('hello')
|
||
})
|
||
|
||
it('findCardById 應該在找不到時返回 undefined', () => {
|
||
const store = useReviewDataStore.getState()
|
||
|
||
const foundCard = store.findCardById('non-existent')
|
||
expect(foundCard).toBeUndefined()
|
||
})
|
||
})
|
||
|
||
describe('resetData', () => {
|
||
it('應該重置所有狀態為初始值', () => {
|
||
const store = useReviewDataStore.getState()
|
||
|
||
// 設置一些狀態
|
||
store.setDueCards(mockDueCards)
|
||
store.setShowComplete(true)
|
||
store.setShowNoDueCards(true)
|
||
store.setLoadingError('錯誤')
|
||
|
||
// 重置
|
||
store.resetData()
|
||
|
||
expect(store.dueCards).toEqual([])
|
||
expect(store.showComplete).toBe(false)
|
||
expect(store.showNoDueCards).toBe(false)
|
||
expect(store.loadingError).toBe(null)
|
||
expect(store.isLoadingCards).toBe(false)
|
||
})
|
||
})
|
||
}) |