@idooel/runtime-context
Version:
Runtime data pool with namespaces, stackable contexts, subscriptions and optional persistence. Vue adapter included.
353 lines (282 loc) • 10.9 kB
text/typescript
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { createDataPool } from '../../src/core/DataPool'
import { Context } from '../../src/core/Context'
describe('DataPool - Edge Cases and Error Handling', () => {
let pool: ReturnType<typeof createDataPool>
beforeEach(() => {
pool = createDataPool()
})
describe('Edge Cases - Data Types', () => {
it('should handle circular references', () => {
const circularObj: any = { name: 'test' }
circularObj.self = circularObj
expect(() => pool.set('test', 'circular', circularObj)).not.toThrow()
expect(pool.get('test', 'circular')).toBe(circularObj)
})
it('should handle very large objects', () => {
const largeObj = {}
for (let i = 0; i < 10000; i++) {
largeObj[`key${i}`] = `value${i}`
}
expect(() => pool.set('test', 'large', largeObj)).not.toThrow()
expect(pool.get('test', 'large')).toEqual(largeObj)
})
it('should handle special number values', () => {
const specialNumbers = {
infinity: Infinity,
negInfinity: -Infinity,
nan: NaN
}
Object.entries(specialNumbers).forEach(([key, value]) => {
expect(() => pool.set('test', key, value)).not.toThrow()
expect(pool.get('test', key)).toBe(value)
})
})
it('should handle date objects', () => {
const now = new Date()
pool.set('test', 'date', now)
expect(pool.get('test', 'date')).toBe(now)
})
it('should handle regular expressions', () => {
const regex = /test/g
pool.set('test', 'regex', regex)
expect(pool.get('test', 'regex')).toBe(regex)
})
it('should handle functions', () => {
const fn = () => 'test'
pool.set('test', 'function', fn)
expect(pool.get('test', 'function')).toBe(fn)
})
it('should handle symbol keys', () => {
const symbol = Symbol('test')
const obj = { [symbol]: 'symbol value' }
expect(() => pool.set('test', 'symbols', obj)).not.toThrow()
expect(pool.get('test', 'symbols')).toBe(obj)
})
})
describe('Edge Cases - Namespace and Key Names', () => {
it('should handle empty namespace', () => {
expect(() => pool.set('', 'key', 'value')).not.toThrow()
expect(pool.get('', 'key')).toBe('value')
})
it('should handle empty key', () => {
expect(() => pool.set('namespace', '', 'value')).not.toThrow()
expect(pool.get('namespace', '')).toBe('value')
})
it('should handle special characters in namespace', () => {
const specialNamespaces = [
'user@domain.com',
'user-id',
'user.id',
'user/id',
'用户', // Chinese characters
'пользователь', // Cyrillic
'🚀' // Emoji
]
specialNamespaces.forEach(ns => {
expect(() => pool.set(ns, 'key', 'value')).not.toThrow()
expect(pool.get(ns, 'key')).toBe('value')
})
})
it('should handle special characters in key', () => {
const specialKeys = [
'user@domain.com',
'user-id',
'user.id',
'user/id',
'用户',
'пользователь',
'🚀'
]
specialKeys.forEach(key => {
expect(() => pool.set('namespace', key, 'value')).not.toThrow()
expect(pool.get('namespace', key)).toBe('value')
})
})
it('should handle very long namespace and key names', () => {
const longString = 'a'.repeat(1000)
expect(() => pool.set(longString, longString, 'value')).not.toThrow()
expect(pool.get(longString, longString)).toBe('value')
})
it('should handle namespace that already has idooel prefix', () => {
pool.set('idooel.user', 'name', 'John')
pool.set('user', 'age', 30)
const snapshot = pool.snapshot({ merged: true })
// Should have only one 'idooel.user' namespace
expect(snapshot['idooel.user']).toBeDefined()
expect(snapshot['idooel.user.idooel.user']).toBeUndefined()
expect(snapshot['idooel.user'].name).toBe('John')
expect(snapshot['idooel.user'].age).toBe(30)
})
})
describe('Edge Cases - Context Operations', () => {
it('should handle deeply nested contexts', () => {
let result: string = ''
const createContext = (depth: number) => {
return pool.createContext({ [`level${depth}`]: { value: depth } })
}
const runNestedContext = (depth: number) => {
const context = createContext(depth)
pool.runInContext(context, () => {
if (depth === 0) {
result = pool.get('level0', 'value')
return
}
runNestedContext(depth - 1)
})
}
runNestedContext(100)
expect(result).toBe(0)
})
it('should handle context with circular initial data', () => {
const circular: any = { name: 'test' }
circular.self = circular
expect(() => {
const context = pool.createContext({ circular })
pool.runInContext(context, () => {
expect(pool.get('circular', 'name')).toBe('test')
})
}).not.toThrow()
})
it('should handle context when error occurs inside', () => {
const context = pool.createContext({ user: { name: 'John' } })
expect(() => {
pool.runInContext(context, () => {
expect(pool.get('user', 'name')).toBe('John')
throw new Error('Test error')
})
}).toThrow('Test error')
// Context should not affect global state
expect(pool.get('user', 'name')).toBeUndefined()
})
})
describe('Edge Cases - Subscriptions', () => {
it('should handle subscription during callback execution', () => {
const callback1 = vi.fn()
const callback2 = vi.fn()
pool.subscribe({ ns: 'user', key: 'name' }, () => {
callback1()
// Subscribe during callback
pool.subscribe({ ns: 'user', key: 'name' }, callback2)
})
pool.set('user', 'name', 'John')
expect(callback1).toHaveBeenCalled()
// In some implementations, new subscription might trigger immediately
// Let's just verify the system doesn't crash
pool.set('user', 'name', 'Jane')
expect(callback2).toHaveBeenCalled()
})
it('should handle unsubscribe during callback execution', () => {
const callback = vi.fn()
let unsubscribe: () => void
unsubscribe = pool.subscribe({ ns: 'user', key: 'name' }, () => {
callback()
unsubscribe()
})
pool.set('user', 'name', 'John')
pool.set('user', 'name', 'Jane')
expect(callback).toHaveBeenCalledTimes(1)
})
it('should handle many subscribers', () => {
const callbacks = Array.from({ length: 1000 }, () => vi.fn())
callbacks.forEach(callback => {
pool.subscribe({ ns: 'user', key: 'name' }, callback)
})
pool.set('user', 'name', 'John')
callbacks.forEach(callback => {
expect(callback).toHaveBeenCalled()
})
})
it('should handle subscription with complex filters', () => {
const callback = vi.fn()
pool.subscribe(
{ ns: 'user', key: 'name' },
callback
)
// Should not trigger
pool.set('admin', 'name', 'Admin')
pool.set('user', 'age', 30)
// Should trigger
pool.set('user', 'name', 'John')
expect(callback).toHaveBeenCalledTimes(1)
})
})
describe('Memory Management', () => {
it('should handle clearing large amounts of data', () => {
// Add a lot of data
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 100; j++) {
pool.set(`namespace${i}`, `key${j}`, `value${i}-${j}`)
}
}
expect(() => pool.clear()).not.toThrow()
// Verify all data is cleared
expect(pool.get('namespace0', 'key0')).toBeUndefined()
expect(pool.get('namespace999', 'key99')).toBeUndefined()
})
it('should handle clearing contexts with large data', () => {
const contexts = []
for (let i = 0; i < 100; i++) {
const context = pool.createContext({
[`data${i}`]: { index: i, data: 'x'.repeat(1000) }
})
contexts.push(context)
}
expect(() => {
contexts.forEach(context => {
pool.runInContext(context, () => {
pool.set(`context${context.id}`, 'value', 'data')
})
})
}).not.toThrow()
})
})
describe('Performance Edge Cases', () => {
it('should handle rapid successive operations', () => {
const start = performance.now()
for (let i = 0; i < 10000; i++) {
pool.set('perf', `key${i}`, `value${i}`)
pool.get('perf', `key${i}`)
}
const end = performance.now()
const duration = end - start
// Should complete within reasonable time (adjust threshold as needed)
expect(duration).toBeLessThan(1000) // 1 second
})
it('should handle rapid context switching', () => {
const contexts = Array.from({ length: 100 }, (_, i) =>
pool.createContext({ [`level${i}`]: { value: i } })
)
const start = performance.now()
for (let i = 0; i < 1000; i++) {
const context = contexts[i % contexts.length]
pool.runInContext(context, () => {
pool.get(`level${i % contexts.length}`, 'value')
})
}
const end = performance.now()
const duration = end - start
expect(duration).toBeLessThan(1000) // 1 second
})
})
describe('Invalid Input Handling', () => {
it('should handle undefined namespace/key gracefully', () => {
// undefined is converted to string 'undefined' by JavaScript
expect(() => pool.set(undefined as any, 'key', 'value')).not.toThrow()
expect(() => pool.set('namespace', undefined as any, 'value')).not.toThrow()
expect(() => pool.get(undefined as any, 'key')).not.toThrow()
expect(() => pool.get('namespace', undefined as any)).not.toThrow()
// Verify the data is stored with 'idooel.undefined' as namespace
expect(pool.get('undefined', 'key')).toBe('value')
// undefined as key should work as string 'undefined'
expect(pool.get('namespace', 'undefined')).toBe('value')
})
it('should handle null values correctly', () => {
pool.set('test', 'nullValue', null)
expect(pool.get('test', 'nullValue')).toBeNull()
pool.set('test', 'explicitNull', null)
pool.delete('test', 'explicitNull')
expect(pool.get('test', 'explicitNull')).toBeUndefined()
})
})
})