UNPKG

@idooel/runtime-context

Version:

Runtime data pool with namespaces, stackable contexts, subscriptions and optional persistence. Vue adapter included.

354 lines (278 loc) 10.7 kB
import { describe, it, expect, beforeEach, vi } from 'vitest' import { createDataPool } from '../../src/core/DataPool' import type { DataEvent } from '../../src/core/types' describe('DataPool - Core Functionality', () => { let pool: ReturnType<typeof createDataPool> beforeEach(() => { pool = createDataPool() }) describe('Basic Operations', () => { it('should set and get values', () => { pool.set('user', 'name', 'John') expect(pool.get('user', 'name')).toBe('John') }) it('should return fallback value for non-existent keys', () => { expect(pool.get('user', 'name', 'default')).toBe('default') expect(pool.get('user', 'nonexistent')).toBeUndefined() }) it('should handle different data types', () => { const testData = { string: 'test', number: 42, boolean: true, object: { key: 'value' }, array: [1, 2, 3], null: null, undefined: undefined } Object.entries(testData).forEach(([key, value]) => { pool.set('test', key, value) expect(pool.get('test', key)).toBe(value) }) }) it('should update existing values', () => { pool.set('user', 'name', 'John') pool.set('user', 'name', 'Jane') expect(pool.get('user', 'name')).toBe('Jane') }) it('should handle multiple namespaces', () => { pool.set('user', 'name', 'John') pool.set('app', 'theme', 'dark') pool.set('settings', 'language', 'en') expect(pool.get('user', 'name')).toBe('John') expect(pool.get('app', 'theme')).toBe('dark') expect(pool.get('settings', 'language')).toBe('en') }) }) describe('Namespace Prefix', () => { it('should automatically add idooel prefix to namespaces', () => { pool.set('user', 'token', 'abc123') // Check internal storage by accessing snapshot const snapshot = pool.snapshot({ merged: true }) expect(snapshot).toHaveProperty('idooel.user') expect(snapshot['idooel.user']).toHaveProperty('token', 'abc123') }) it('should not double-prefix existing idooel namespaces', () => { pool.set('idooel.user', 'token', 'xyz789') const snapshot = pool.snapshot({ merged: true }) expect(snapshot).toHaveProperty('idooel.user') expect(snapshot['idooel.user'].token).toBe('xyz789') }) it('should access values correctly regardless of prefix in user code', () => { pool.set('user', 'data', 'value') pool.set('idooel.user', 'prefixed', 'data') // Both should work from user perspective expect(pool.get('user', 'data')).toBe('value') expect(pool.get('user', 'prefixed')).toBe('data') }) }) describe('Delete Operations', () => { it('should delete specific keys', () => { pool.set('user', 'name', 'John') pool.set('user', 'age', 30) pool.delete('user', 'name') expect(pool.get('user', 'name')).toBeUndefined() expect(pool.get('user', 'age')).toBe(30) }) it('should handle deleting non-existent keys', () => { expect(() => pool.delete('user', 'nonexistent')).not.toThrow() }) it('should handle deleting from non-existent namespaces', () => { expect(() => pool.delete('nonexistent', 'key')).not.toThrow() }) }) describe('Clear Operations', () => { it('should clear specific namespace', () => { pool.set('user', 'name', 'John') pool.set('user', 'age', 30) pool.set('app', 'theme', 'dark') pool.clear('user') expect(pool.get('user', 'name')).toBeUndefined() expect(pool.get('user', 'age')).toBeUndefined() expect(pool.get('app', 'theme')).toBe('dark') }) it('should clear all namespaces', () => { pool.set('user', 'name', 'John') pool.set('app', 'theme', 'dark') pool.set('settings', 'language', 'en') pool.clear() expect(pool.get('user', 'name')).toBeUndefined() expect(pool.get('app', 'theme')).toBeUndefined() expect(pool.get('settings', 'language')).toBeUndefined() }) }) describe('Subscription System', () => { it('should notify subscribers of data changes', () => { const callback = vi.fn() pool.subscribe({ ns: 'user', key: 'name' }, callback) pool.set('user', 'name', 'John') expect(callback).toHaveBeenCalledTimes(1) expect(callback).toHaveBeenCalledWith( expect.objectContaining({ ns: 'user', // Should be original namespace, not prefixed key: 'name', value: 'John', source: 'global' }) ) }) it('should notify subscribers of data deletion', () => { pool.set('user', 'name', 'John') const callback = vi.fn() pool.subscribe({ ns: 'user', key: 'name' }, callback) pool.delete('user', 'name') expect(callback).toHaveBeenCalledTimes(1) expect(callback).toHaveBeenCalledWith( expect.objectContaining({ ns: 'user', key: 'name', value: undefined, source: 'delete' }) ) }) it('should notify subscribers of namespace clearing', () => { pool.set('user', 'name', 'John') pool.set('user', 'age', 30) const callback = vi.fn() // Subscribe to all changes in user namespace to catch clear events pool.subscribe({ ns: 'user' }, callback) pool.clear('user') expect(callback).toHaveBeenCalledWith( expect.objectContaining({ ns: 'user', key: '*', value: undefined, source: 'clear' }) ) }) it('should support wildcard subscriptions', () => { const callback = vi.fn() pool.subscribe({}, callback) // Subscribe to all changes pool.set('user', 'name', 'John') pool.set('app', 'theme', 'dark') pool.delete('user', 'name') expect(callback).toHaveBeenCalledTimes(3) }) it('should support namespace-only subscriptions', () => { const callback = vi.fn() pool.subscribe({ ns: 'user' }, callback) pool.set('user', 'name', 'John') pool.set('user', 'age', 30) pool.set('app', 'theme', 'dark') // Should not trigger expect(callback).toHaveBeenCalledTimes(2) }) it('should handle unsubscribe correctly', () => { const callback = vi.fn() const unsubscribe = pool.subscribe({ ns: 'user', key: 'name' }, callback) unsubscribe() pool.set('user', 'name', 'John') expect(callback).not.toHaveBeenCalled() }) it('should handle subscriber errors gracefully', () => { const errorCallback = vi.fn(() => { throw new Error('Subscriber error') }) const normalCallback = vi.fn() pool.subscribe({ ns: 'user', key: 'name' }, errorCallback) pool.subscribe({ ns: 'user', key: 'name' }, normalCallback) expect(() => pool.set('user', 'name', 'John')).not.toThrow() expect(normalCallback).toHaveBeenCalled() }) }) describe('Context System', () => { it('should create context with initial data', () => { const context = pool.createContext({ user: { name: 'John', role: 'admin' }, app: { theme: 'dark' } }) expect(context).toBeDefined() expect(context.id).toBeDefined() }) it('should run operations in context', () => { pool.set('global', 'value', 'global') const context = pool.createContext({ global: { value: 'context' } }) pool.runInContext(context, () => { expect(pool.get('global', 'value')).toBe('context') }) expect(pool.get('global', 'value')).toBe('global') }) it('should support nested contexts', () => { const outerContext = pool.createContext({ user: { name: 'John' } }) const innerContext = pool.createContext({ user: { age: 30 } }) pool.runInContext(outerContext, () => { expect(pool.get('user', 'name')).toBe('John') expect(pool.get('user', 'age')).toBeUndefined() pool.runInContext(innerContext, () => { expect(pool.get('user', 'name')).toBe('John') expect(pool.get('user', 'age')).toBe(30) }) expect(pool.get('user', 'age')).toBeUndefined() }) }) it('should handle context-specific data setting', () => { const context = pool.createContext() pool.runInContext(context, () => { pool.set('context', 'data', 'context-value') }) expect(pool.get('context', 'data')).toBeUndefined() // Not in global }) it('should support withContext helper', () => { const context = pool.createContext({ user: { name: 'Jane' } }) const contextFn = pool.withContext(context) const result = contextFn(() => pool.get('user', 'name'))() expect(result).toBe('Jane') }) it('should wrap handlers with current context', () => { const context = pool.createContext({ user: { name: 'ContextUser' } }) pool.runInContext(context, () => { const handler = pool.wrapHandler(() => pool.get('user', 'name')) const result = handler() expect(result).toBe('ContextUser') }) }) }) describe('Snapshot Functionality', () => { it('should create merged snapshot', () => { pool.set('user', 'name', 'John') pool.set('app', 'theme', 'dark') const snapshot = pool.snapshot({ merged: true }) expect(snapshot).toEqual({ 'idooel.user': { name: 'John' }, 'idooel.app': { theme: 'dark' } }) }) it('should create detailed snapshot', () => { pool.set('user', 'name', 'John') const context = pool.createContext({ user: { role: 'admin' } }) // Simulate context being active const token = pool.enter(context) pool.set('user', 'age', 30) // Get snapshot while context is still active const snapshot = pool.snapshot({ merged: false }) pool.exit(token) expect(snapshot.global).toEqual({ 'idooel.user': { name: 'John' } }) expect(snapshot.contexts).toHaveLength(1) expect(snapshot.contexts[0].data).toEqual({ 'idooel.user': { role: 'admin', age: 30 } }) }) it('should handle empty pool snapshot', () => { const snapshot = pool.snapshot({ merged: true }) expect(snapshot).toEqual({}) }) }) })