【同步】前端项目源码

【修复】工作流兼容问题
This commit is contained in:
chudong
2025-05-10 11:53:11 +08:00
parent c514471adc
commit f1a75afaba
584 changed files with 55714 additions and 110 deletions

View File

@@ -0,0 +1,295 @@
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { nextTick, ref } from 'vue'
import useSessionStorage from '../src/session-storage'
describe('useSessionStorage', () => {
// 保存原始的sessionStorage
const originalSessionStorage = window.sessionStorage
let sessionStorageMockEvents = []
beforeEach(() => {
// 重置事件数组
sessionStorageMockEvents = []
// 创建模拟的sessionStorage
const sessionStorageMock = {
store: {},
getItem: vi.fn((key) => sessionStorageMock.store[key] || null),
setItem: vi.fn((key, value) => {
const oldValue = sessionStorageMock.store[key]
sessionStorageMock.store[key] = value
// 触发storage事件
const event = new CustomEvent('storage', {
detail: {
key,
oldValue,
newValue: value,
storageArea: sessionStorageMock,
url: window.location.href,
},
})
sessionStorageMockEvents.push(event)
window.dispatchEvent(event)
}),
removeItem: vi.fn((key) => {
const oldValue = sessionStorageMock.store[key]
delete sessionStorageMock.store[key]
// 触发storage事件
const event = new CustomEvent('storage', {
detail: {
key,
oldValue,
newValue: null,
storageArea: sessionStorageMock,
url: window.location.href,
},
})
sessionStorageMockEvents.push(event)
window.dispatchEvent(event)
}),
clear: vi.fn(() => {
sessionStorageMock.store = {}
// 触发storage事件
const event = new CustomEvent('storage', {
detail: {
key: null,
oldValue: null,
newValue: null,
storageArea: sessionStorageMock,
url: window.location.href,
},
})
sessionStorageMockEvents.push(event)
window.dispatchEvent(event)
}),
key: vi.fn((index) => Object.keys(sessionStorageMock.store)[index] || null),
get length() {
return Object.keys(sessionStorageMock.store).length
},
}
// 替换全局的sessionStorage
Object.defineProperty(window, 'sessionStorage', {
value: sessionStorageMock,
writable: true,
})
// 监视console.error
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.spyOn(console, 'warn').mockImplementation(() => {})
// 添加事件监听器模拟
vi.spyOn(window, 'addEventListener')
vi.spyOn(window, 'removeEventListener')
})
afterEach(() => {
// 恢复原始的sessionStorage
Object.defineProperty(window, 'sessionStorage', {
value: originalSessionStorage,
writable: true,
})
vi.restoreAllMocks()
})
it('应该使用sessionStorage存储数据', async () => {
const value = useSessionStorage('testKey', { name: 'test' })
// 验证初始值已存储到sessionStorage
expect(window.sessionStorage.setItem).toHaveBeenCalled()
// 修改值
value.value = { name: 'updated' }
await nextTick()
// 验证更新的值已存储到sessionStorage
const lastCall = window.sessionStorage.setItem.mock.calls.pop()
expect(lastCall[0]).toBe('testKey')
expect(JSON.parse(lastCall[1]).value).toEqual({ name: 'updated' })
})
it('应该从sessionStorage加载存储的数据', () => {
// 预先设置sessionStorage的值
const storedValue = { value: { name: 'stored' } }
window.sessionStorage.getItem.mockReturnValueOnce(JSON.stringify(storedValue))
const value = useSessionStorage('testKey', { name: 'default' })
// 验证加载了存储的值而不是默认值
expect(value.value).toEqual({ name: 'stored' })
expect(window.sessionStorage.getItem).toHaveBeenCalledWith('testKey')
})
it('当设置为null时应该从sessionStorage移除数据', async () => {
const value = useSessionStorage('testKey', { name: 'test' })
// 设置为null
value.value = null
await nextTick()
// 验证数据已从sessionStorage中移除
expect(window.sessionStorage.removeItem).toHaveBeenCalledWith('testKey')
})
it('应该支持过期时间选项', () => {
vi.useFakeTimers()
const now = Date.now()
vi.setSystemTime(now)
useSessionStorage('testKey', 'test', { expires: 1000 })
// 验证设置了过期时间
const lastCall = window.sessionStorage.setItem.mock.calls.pop()
const storedData = JSON.parse(lastCall[1])
expect(storedData.expires).toBe(now + 1000)
vi.useRealTimers()
})
it('应该和localStorage使用不同的存储空间', async () => {
// 创建模拟的localStorage
const localStorageMock = {
store: {},
getItem: vi.fn((key) => localStorageMock.store[key] || null),
setItem: vi.fn((key, value) => {
localStorageMock.store[key] = value
}),
removeItem: vi.fn((key) => {
delete localStorageMock.store[key]
}),
clear: vi.fn(() => {
localStorageMock.store = {}
}),
key: vi.fn((index) => Object.keys(localStorageMock.store)[index] || null),
length: 0,
}
// 替换全局的localStorage
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
writable: true,
})
// 在sessionStorage中保存数据
const sessionValue = useSessionStorage('sameKey', 'sessionValue')
// 确认数据保存在sessionStorage而不是localStorage
expect(window.sessionStorage.setItem).toHaveBeenCalled()
expect(window.localStorage.setItem).not.toHaveBeenCalled()
// 修改sessionStorage中的值
sessionValue.value = 'updatedSessionValue'
await nextTick()
// 验证值被更新到了sessionStorage而不是localStorage
expect(window.sessionStorage.setItem).toHaveBeenCalledTimes(2)
expect(window.localStorage.setItem).not.toHaveBeenCalled()
})
it('应该处理存储的数据格式不正确的情况', () => {
// 模拟sessionStorage中存在无效的JSON数据
window.sessionStorage.getItem.mockReturnValueOnce('invalid json')
// 使用带有默认值的hook
const value = useSessionStorage('testKey', 'default')
// 验证使用了默认值
expect(value.value).toBe('default')
// 验证错误被记录
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Error parsing'), expect.any(Error))
})
it('应该在sessionStorage不可用时使用内存存储', () => {
// 模拟sessionStorage不可用的情况
window.sessionStorage.setItem.mockImplementationOnce(() => {
throw new Error('QuotaExceededError')
})
const value = useSessionStorage('testKey', 'default')
// 验证值仍然可用
expect(value.value).toBe('default')
// 修改值
value.value = 'updated'
// 验证值被更新内存中即使无法保存到sessionStorage
expect(value.value).toBe('updated')
// 验证警告被记录
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('sessionStorage error'), expect.any(Error))
})
it('应该在达到过期时间时使用默认值', () => {
vi.useFakeTimers()
const now = Date.now()
vi.setSystemTime(now)
// 模拟在sessionStorage中有一个过期的值
const expiredData = {
value: 'expired',
expires: now - 1000, // 已过期1秒
}
window.sessionStorage.getItem.mockReturnValueOnce(JSON.stringify(expiredData))
// 使用hook
const value = useSessionStorage('testKey', 'default')
// 验证使用了默认值而不是过期的值
expect(value.value).toBe('default')
vi.useRealTimers()
})
it('应该监听storage事件同步其他标签页的变化', async () => {
// 创建两个使用相同键的hooks实例
const value1 = useSessionStorage('syncKey', 'initial')
const value2 = useSessionStorage('syncKey', 'other')
// 验证它们有相同的初始值
expect(value1.value).toBe('initial')
expect(value2.value).toBe('initial')
// 修改第一个实例的值
value1.value = 'updated'
await nextTick()
// 验证第二个实例的值也更新了
expect(value2.value).toBe('updated')
// 模拟从另一个标签页修改同一个键
const event = new StorageEvent('storage', {
key: 'syncKey',
newValue: JSON.stringify({ value: 'from another tab' }),
storageArea: window.sessionStorage,
})
window.dispatchEvent(event)
await nextTick()
// 验证两个实例都更新了值
expect(value1.value).toBe('from another tab')
expect(value2.value).toBe('from another tab')
})
it('应该支持自定义序列化和反序列化', () => {
// 创建自定义的序列化和反序列化函数
const serializer = {
serialize: vi.fn((value) => `custom:${JSON.stringify(value)}`),
deserialize: vi.fn((value) => {
if (value && value.startsWith('custom:')) {
return JSON.parse(value.slice(7))
}
return null
}),
}
// 使用自定义序列化器
const value = useSessionStorage(
'customKey',
{ test: true },
{
serializer,
},
)
// 验证自定义序列化器被使用
expect(serializer.serialize).toHaveBeenCalled()
// 检查存储的数据格式
const lastCall = window.sessionStorage.setItem.mock.calls.pop()
expect(lastCall[1]).toContain('custom:')
// 模拟从存储加载数据
window.sessionStorage.getItem.mockReturnValueOnce('custom:{"result":"loaded"}')
// 创建一个新的hook实例使用相同的键和序列化器
const loadedValue = useSessionStorage('customKey', null, { serializer })
// 验证反序列化器被使用
expect(serializer.deserialize).toHaveBeenCalled()
expect(loadedValue.value).toEqual({ result: 'loaded' })
})
it('应该支持使用ref作为默认值', async () => {
const defaultValueRef = ref('initialRef')
// 使用ref作为默认值
const value = useSessionStorage('refKey', defaultValueRef)
// 验证初始值
expect(value.value).toBe('initialRef')
// 修改ref的值
defaultValueRef.value = 'updatedRef'
await nextTick()
// 验证存储的值没有变化,只是默认值变化了
expect(value.value).toBe('initialRef') // 已经初始化的值不会随着默认值ref变化
// 创建一个新的带有相同键的实例但没有初始值
window.sessionStorage.getItem.mockReturnValueOnce(null)
const newValue = useSessionStorage('newRefKey', defaultValueRef)
// 验证新实例使用了当前的ref值
expect(newValue.value).toBe('updatedRef')
})
it('应该在组件卸载时取消事件监听', () => {
// 使用onUnmounted来模拟组件卸载
const unmountHandlers = []
vi.mock('vue', async () => {
const actual = await vi.importActual('vue')
return {
...actual,
onUnmounted: (fn) => unmountHandlers.push(fn),
}
})
// 创建hook实例
useSessionStorage('testKey', 'value')
// 验证添加了事件监听器
expect(window.addEventListener).toHaveBeenCalledWith('storage', expect.any(Function))
// 模拟组件卸载
unmountHandlers.forEach((handler) => handler())
// 验证移除了事件监听器
expect(window.removeEventListener).toHaveBeenCalledWith('storage', expect.any(Function))
})
})
//# sourceMappingURL=session-storage.spec.js.map