allinssl/frontend/packages/vue/hooks/test/session-storage.spec.js

296 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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