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

243 lines
7.4 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useTestQueueStore, TestItem, ReviewMode } from '../useTestQueueStore'
import { mockDueCards } from '@/lib/mock/reviewMockData'
// Mock dependencies
vi.mock('@/lib/utils/cefrUtils', () => ({
getReviewTypesByCEFR: vi.fn(() => ['flip-memory', 'vocab-choice'])
}))
vi.mock('@/lib/mock/reviewMockData', () => ({
isTestMode: vi.fn(),
getTestModeReviewTypes: vi.fn(() => ['flip-memory', 'vocab-choice']),
mockDueCards
}))
describe('useTestQueueStore', () => {
beforeEach(() => {
// 重置 store
useTestQueueStore.getState().resetQueue()
vi.clearAllMocks()
// Mock localStorage
vi.mocked(localStorage.getItem).mockReturnValue('A2')
})
describe('初始狀態', () => {
it('應該有正確的初始值', () => {
const state = useTestQueueStore.getState()
expect(state.testItems).toEqual([])
expect(state.currentTestIndex).toBe(0)
expect(state.completedTests).toBe(0)
expect(state.totalTests).toBe(0)
expect(state.currentMode).toBe('flip-memory')
expect(state.skippedTests).toEqual(new Set())
})
})
describe('initializeTestQueue', () => {
it('應該正確生成測驗項目', () => {
const store = useTestQueueStore.getState()
const testCards = [mockDueCards[0]] // 只用一張卡片測試
store.initializeTestQueue(testCards, [])
expect(store.testItems).toHaveLength(2) // 1卡 * 2測驗類型
expect(store.totalTests).toBe(2)
expect(store.currentTestIndex).toBe(0)
// 檢查第一個測驗項目
const firstTest = store.testItems[0]
expect(firstTest.cardId).toBe('mock-1')
expect(firstTest.word).toBe('hello')
expect(firstTest.isCurrent).toBe(true)
expect(firstTest.isCompleted).toBe(false)
})
it('應該在測試模式下使用簡化邏輯', () => {
vi.mocked(require('@/lib/mock/reviewMockData').isTestMode).mockReturnValue(true)
const store = useTestQueueStore.getState()
store.initializeTestQueue([mockDueCards[0]], [])
// 驗證使用了測試模式的測驗類型
expect(store.testItems.map(item => item.testType))
.toEqual(['flip-memory', 'vocab-choice'])
})
it('應該過濾已完成的測驗', () => {
const store = useTestQueueStore.getState()
const completedTests = [
{ flashcardId: 'mock-1', testType: 'flip-memory' }
]
store.initializeTestQueue([mockDueCards[0]], completedTests)
expect(store.testItems).toHaveLength(1) // 只剩 vocab-choice
expect(store.testItems[0].testType).toBe('vocab-choice')
})
})
describe('goToNextTest', () => {
beforeEach(() => {
const store = useTestQueueStore.getState()
store.initializeTestQueue([mockDueCards[0]], [])
})
it('應該正確跳轉到下一個測驗', () => {
const store = useTestQueueStore.getState()
store.goToNextTest()
expect(store.currentTestIndex).toBe(1)
expect(store.currentMode).toBe('vocab-choice')
expect(store.testItems[0].isCurrent).toBe(false)
expect(store.testItems[1].isCurrent).toBe(true)
})
it('應該在最後一個測驗時保持不變', () => {
const store = useTestQueueStore.getState()
// 跳到最後一個測驗
store.setCurrentTestIndex(1)
store.goToNextTest()
// 應該保持在最後一個
expect(store.currentTestIndex).toBe(1)
})
})
describe('markTestCompleted', () => {
beforeEach(() => {
const store = useTestQueueStore.getState()
store.initializeTestQueue([mockDueCards[0]], [])
})
it('應該正確標記測驗為完成', () => {
const store = useTestQueueStore.getState()
store.markTestCompleted(0)
expect(store.testItems[0].isCompleted).toBe(true)
expect(store.testItems[0].isCurrent).toBe(false)
expect(store.completedTests).toBe(1)
})
it('應該從跳過列表移除完成的測驗', () => {
const store = useTestQueueStore.getState()
// 先跳過一個測驗
store.skipCurrentTest()
const skippedId = store.testItems[0].id
// 然後完成這個測驗
store.markTestCompleted(0)
expect(store.skippedTests.has(skippedId)).toBe(false)
})
})
describe('skipCurrentTest', () => {
beforeEach(() => {
const store = useTestQueueStore.getState()
store.initializeTestQueue([mockDueCards[0], mockDueCards[1]], [])
})
it('應該正確跳過當前測驗', () => {
const store = useTestQueueStore.getState()
const currentTestId = store.testItems[0].id
store.skipCurrentTest()
expect(store.testItems[0].isSkipped).toBe(true)
expect(store.testItems[0].skippedAt).toBeDefined()
expect(store.skippedTests.has(currentTestId)).toBe(true)
})
it('應該正確切換到下一個可用測驗', () => {
const store = useTestQueueStore.getState()
store.skipCurrentTest()
// 應該跳過被跳過的測驗,找到下一個
expect(store.testItems[store.currentTestIndex].isCompleted).toBe(false)
expect(store.testItems[store.currentTestIndex].isCurrent).toBe(true)
})
})
describe('工具方法', () => {
beforeEach(() => {
const store = useTestQueueStore.getState()
store.initializeTestQueue(mockDueCards, [])
})
it('getTestStats 應該返回正確的統計', () => {
const store = useTestQueueStore.getState()
// 完成一個測驗
store.markTestCompleted(0)
// 跳過一個測驗
store.skipCurrentTest()
const stats = store.getTestStats()
expect(stats.total).toBe(6) // 3卡 * 2測驗
expect(stats.completed).toBe(1)
expect(stats.skipped).toBe(1)
expect(stats.remaining).toBe(4)
})
it('isAllTestsCompleted 應該正確檢測完成狀態', () => {
const store = useTestQueueStore.getState()
expect(store.isAllTestsCompleted()).toBe(false)
// 完成所有測驗
store.testItems.forEach((_, index) => {
store.markTestCompleted(index)
})
expect(store.isAllTestsCompleted()).toBe(true)
})
})
describe('優先級演算法', () => {
it('未嘗試的測驗應該有最高優先級', () => {
const store = useTestQueueStore.getState()
store.initializeTestQueue([mockDueCards[0]], [])
const firstTest = store.testItems[0]
expect(firstTest.priority).toBe(100)
})
it('跳過的測驗應該有較低優先級', () => {
const store = useTestQueueStore.getState()
store.initializeTestQueue([mockDueCards[0]], [])
store.skipCurrentTest()
const skippedTest = store.testItems.find(item => item.isSkipped)
expect(skippedTest?.priority).toBe(10)
})
})
describe('resetQueue', () => {
it('應該重置所有隊列狀態', () => {
const store = useTestQueueStore.getState()
// 設置一些狀態
store.initializeTestQueue(mockDueCards, [])
store.markTestCompleted(0)
store.skipCurrentTest()
// 重置
store.resetQueue()
expect(store.testItems).toEqual([])
expect(store.currentTestIndex).toBe(0)
expect(store.completedTests).toBe(0)
expect(store.totalTests).toBe(0)
expect(store.skippedTests).toEqual(new Set())
})
})
})