dramaling-vocab-learning/frontend/store/review/__tests__/useReviewDataStore.test.ts

202 lines
6.3 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 { 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)
})
})
})