280 lines
8.2 KiB
TypeScript
280 lines
8.2 KiB
TypeScript
/**
|
|
* PlaybackProgressManager 单元测试
|
|
*/
|
|
|
|
import PlaybackProgressManager from '@/lib/3d/playback-progress-manager'
|
|
|
|
// Mock localStorage
|
|
const mockLocalStorage = (() => {
|
|
let store: Record<string, string> = {}
|
|
return {
|
|
getItem: jest.fn((key: string) => store[key] || null),
|
|
setItem: jest.fn((key: string, value: string) => { store[key] = value }),
|
|
removeItem: jest.fn((key: string) => { delete store[key] }),
|
|
clear: jest.fn(() => { store = {} }),
|
|
}
|
|
})()
|
|
|
|
Object.defineProperty(global, 'localStorage', { value: mockLocalStorage })
|
|
|
|
describe('PlaybackProgressManager', () => {
|
|
let manager: PlaybackProgressManager
|
|
|
|
beforeEach(() => {
|
|
mockLocalStorage.clear()
|
|
manager = new PlaybackProgressManager({
|
|
expirationTime: 7 * 24 * 60 * 60 * 1000,
|
|
maxSavedProgress: 50,
|
|
autoCleanup: false,
|
|
})
|
|
})
|
|
|
|
describe('保存和获取进度', () => {
|
|
it('应该正确保存播放进度', () => {
|
|
manager.saveProgress('product-1', 5, 10, 1)
|
|
|
|
const progress = manager.getProgress('product-1')
|
|
|
|
expect(progress).not.toBeNull()
|
|
expect(progress?.productId).toBe('product-1')
|
|
expect(progress?.currentStepIndex).toBe(5)
|
|
expect(progress?.totalSteps).toBe(10)
|
|
expect(progress?.playbackSpeed).toBe(1)
|
|
expect(progress?.isCompleted).toBe(false)
|
|
})
|
|
|
|
it('应该在步骤到达最后时标记为完成', () => {
|
|
manager.saveProgress('product-1', 9, 10, 1)
|
|
|
|
const progress = manager.getProgress('product-1')
|
|
|
|
expect(progress?.isCompleted).toBe(true)
|
|
})
|
|
|
|
it('获取不存在的进度应该返回 null', () => {
|
|
const progress = manager.getProgress('non-existent')
|
|
|
|
expect(progress).toBeNull()
|
|
})
|
|
|
|
it('应该正确更新已存在的进度', () => {
|
|
manager.saveProgress('product-1', 3, 10, 1)
|
|
manager.saveProgress('product-1', 7, 10, 2)
|
|
|
|
const progress = manager.getProgress('product-1')
|
|
|
|
expect(progress?.currentStepIndex).toBe(7)
|
|
expect(progress?.playbackSpeed).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('检查进度', () => {
|
|
it('hasProgress 应该正确检测', () => {
|
|
expect(manager.hasProgress('product-1')).toBe(false)
|
|
|
|
manager.saveProgress('product-1', 5, 10)
|
|
|
|
expect(manager.hasProgress('product-1')).toBe(true)
|
|
})
|
|
|
|
it('hasValidProgress 应该排除已完成的进度', () => {
|
|
manager.saveProgress('product-1', 9, 10) // 完成
|
|
manager.saveProgress('product-2', 5, 10) // 未完成
|
|
manager.saveProgress('product-3', 0, 10) // 第0步
|
|
|
|
expect(manager.hasValidProgress('product-1')).toBe(false)
|
|
expect(manager.hasValidProgress('product-2')).toBe(true)
|
|
expect(manager.hasValidProgress('product-3')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('删除进度', () => {
|
|
it('应该正确删除进度', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
|
|
const result = manager.deleteProgress('product-1')
|
|
|
|
expect(result).toBe(true)
|
|
expect(manager.hasProgress('product-1')).toBe(false)
|
|
})
|
|
|
|
it('删除不存在的进度应该返回 false', () => {
|
|
const result = manager.deleteProgress('non-existent')
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('clearAll 应该清除所有进度', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
manager.saveProgress('product-2', 3, 8)
|
|
|
|
manager.clearAll()
|
|
|
|
expect(manager.getAllProgress().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('标记完成', () => {
|
|
it('应该正确标记进度为完成', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
|
|
manager.markCompleted('product-1')
|
|
|
|
const progress = manager.getProgress('product-1')
|
|
expect(progress?.isCompleted).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('进度百分比', () => {
|
|
it('应该正确计算进度百分比', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
|
|
expect(manager.getProgressPercent('product-1')).toBe(50)
|
|
})
|
|
|
|
it('步骤为0时应该返回0', () => {
|
|
manager.saveProgress('product-1', 0, 10)
|
|
|
|
expect(manager.getProgressPercent('product-1')).toBe(0)
|
|
})
|
|
|
|
it('不存在的产品应该返回0', () => {
|
|
expect(manager.getProgressPercent('non-existent')).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('获取所有进度', () => {
|
|
it('应该返回所有保存的进度', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
manager.saveProgress('product-2', 3, 8)
|
|
manager.saveProgress('product-3', 7, 15)
|
|
|
|
const allProgress = manager.getAllProgress()
|
|
|
|
expect(allProgress.length).toBe(3)
|
|
})
|
|
|
|
it('应该按时间戳降序排序', async () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
manager.saveProgress('product-2', 3, 8)
|
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
manager.saveProgress('product-3', 7, 15)
|
|
|
|
const allProgress = manager.getAllProgress()
|
|
|
|
// 最后保存的应该在最前面
|
|
expect(allProgress[0].productId).toBe('product-3')
|
|
})
|
|
})
|
|
|
|
describe('恢复提示信息', () => {
|
|
it('应该生成正确的恢复提示信息', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
|
|
const info = manager.getResumePromptInfo('product-1')
|
|
|
|
expect(info).not.toBeNull()
|
|
expect(info?.hasProgress).toBe(true)
|
|
expect(info?.stepInfo).toBe('第 6/10 步')
|
|
expect(info?.percent).toBe(50)
|
|
})
|
|
|
|
it('已完成的进度应该返回 null', () => {
|
|
manager.saveProgress('product-1', 9, 10)
|
|
|
|
const info = manager.getResumePromptInfo('product-1')
|
|
|
|
expect(info).toBeNull()
|
|
})
|
|
|
|
it('第0步的进度应该返回 null', () => {
|
|
manager.saveProgress('product-1', 0, 10)
|
|
|
|
const info = manager.getResumePromptInfo('product-1')
|
|
|
|
expect(info).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('时间格式化', () => {
|
|
it('应该格式化"刚刚"', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
|
|
const timeStr = manager.formatLastPlayedTime('product-1')
|
|
|
|
expect(timeStr).toBe('刚刚')
|
|
})
|
|
|
|
it('不存在的产品应该返回空字符串', () => {
|
|
const timeStr = manager.formatLastPlayedTime('non-existent')
|
|
|
|
expect(timeStr).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('清理过期进度', () => {
|
|
it('应该清理过期的进度', () => {
|
|
// 创建一个带短过期时间的管理器
|
|
const shortExpirationManager = new PlaybackProgressManager({
|
|
expirationTime: 1, // 1ms
|
|
autoCleanup: false,
|
|
})
|
|
|
|
shortExpirationManager.saveProgress('product-1', 5, 10)
|
|
|
|
// 等待进度过期
|
|
return new Promise<void>((resolve) => {
|
|
setTimeout(() => {
|
|
const cleaned = shortExpirationManager.cleanupExpired()
|
|
expect(cleaned).toBeGreaterThanOrEqual(0)
|
|
resolve()
|
|
}, 10)
|
|
})
|
|
})
|
|
|
|
it('应该限制最大保存数量', () => {
|
|
const limitedManager = new PlaybackProgressManager({
|
|
maxSavedProgress: 3,
|
|
autoCleanup: false,
|
|
})
|
|
|
|
limitedManager.saveProgress('product-1', 1, 10)
|
|
limitedManager.saveProgress('product-2', 2, 10)
|
|
limitedManager.saveProgress('product-3', 3, 10)
|
|
limitedManager.saveProgress('product-4', 4, 10)
|
|
limitedManager.saveProgress('product-5', 5, 10)
|
|
|
|
limitedManager.cleanupExpired()
|
|
|
|
// 应该只保留最新的3个
|
|
expect(limitedManager.getAllProgress().length).toBeLessThanOrEqual(3)
|
|
})
|
|
})
|
|
|
|
describe('存储', () => {
|
|
it('应该持久化到 localStorage', () => {
|
|
manager.saveProgress('product-1', 5, 10)
|
|
|
|
expect(mockLocalStorage.setItem).toHaveBeenCalled()
|
|
})
|
|
|
|
it('应该从 localStorage 加载', () => {
|
|
// 预设数据
|
|
const presetData = [{
|
|
productId: 'product-1',
|
|
currentStepIndex: 5,
|
|
totalSteps: 10,
|
|
timestamp: Date.now(),
|
|
playbackSpeed: 1,
|
|
isCompleted: false,
|
|
}]
|
|
mockLocalStorage.getItem.mockReturnValueOnce(JSON.stringify(presetData))
|
|
|
|
const newManager = new PlaybackProgressManager({ autoCleanup: false })
|
|
|
|
expect(newManager.hasProgress('product-1')).toBe(true)
|
|
})
|
|
})
|
|
})
|