UNPKG

@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
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() }) }) })