UNPKG

@idiosync/react-observable

Version:

State management control layer for React projects

313 lines (312 loc) 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const persistent_observable_1 = require("./persistent-observable"); // Mock storage implementation const mockStorage = { data: new Map(), getItem: jest.fn((key) => mockStorage.data.get(key) || null), setItem: jest.fn((key, value) => mockStorage.data.set(key, value)), removeItem: jest.fn((key) => mockStorage.data.delete(key)), clear: jest.fn(() => mockStorage.data.clear()), }; describe('createPersistentObservable', () => { beforeEach(() => { mockStorage.data.clear(); jest.clearAllMocks(); }); describe('Basic functionality', () => { it('should create a persistent observable', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); expect(obs.get()).toBe('default'); }); it('should load value from storage on creation', () => { mockStorage.setItem('test-key', JSON.stringify('stored value')); const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); expect(obs.get()).toBe('stored value'); expect(mockStorage.getItem).toHaveBeenCalledWith('test-key'); }); it('should use initial value when storage is empty', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); expect(obs.get()).toBe('default'); expect(mockStorage.getItem).toHaveBeenCalledWith('test-key'); }); it('should handle function initial value', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: () => 'function default', }); expect(obs.get()).toBe('function default'); }); }); describe('Persistence behavior', () => { it('should save value to storage when set', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); obs.set('new value'); expect(mockStorage.setItem).toHaveBeenCalledWith('test-key', JSON.stringify('new value')); }); it('should save complex objects to storage', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: { name: 'default' }, }); const complexValue = { name: 'John', age: 25, active: true }; obs.set(complexValue); expect(mockStorage.setItem).toHaveBeenCalledWith('test-key', JSON.stringify(complexValue)); }); it('should save arrays to storage', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: [], }); const arrayValue = [1, 2, 3, 'test']; obs.set(arrayValue); expect(mockStorage.setItem).toHaveBeenCalledWith('test-key', JSON.stringify(arrayValue)); }); it('should handle function setter', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 10, }); obs.set((current) => current + 5); expect(mockStorage.setItem).toHaveBeenCalledWith('test-key', JSON.stringify(15)); }); }); describe('Hydration', () => { it('should hydrate from storage correctly', () => { const storedData = { user: 'John', settings: { theme: 'dark' } }; mockStorage.setItem('test-key', JSON.stringify(storedData)); const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: { user: 'default' }, }); expect(obs.get()).toEqual(storedData); }); it('should handle invalid JSON in storage', () => { mockStorage.setItem('test-key', 'invalid json'); const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); expect(obs.get()).toBe('default'); }); it('should handle null values from storage', () => { mockStorage.setItem('test-key', JSON.stringify(null)); const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); expect(obs.get()).toBeNull(); }); it('should handle undefined values from storage', () => { mockStorage.setItem('test-key', JSON.stringify(undefined)); const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); expect(obs.get()).toBeUndefined(); }); }); describe('Storage error handling', () => { it('should handle storage get errors gracefully', () => { const errorStorage = { ...mockStorage, getItem: jest.fn(() => { throw new Error('Storage error'); }), }; const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: errorStorage, initialValue: 'default', }); expect(obs.get()).toBe('default'); }); it('should handle storage set errors gracefully', () => { const errorStorage = { ...mockStorage, setItem: jest.fn(() => { throw new Error('Storage error'); }), }; const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: errorStorage, initialValue: 'default', }); // Should not throw error expect(() => obs.set('new value')).not.toThrow(); expect(obs.get()).toBe('new value'); }); }); describe('Observable functionality', () => { it('should maintain all observable functionality', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); const listener = jest.fn(); obs.subscribe(listener); obs.set('new value'); expect(listener).toHaveBeenCalledWith('new value', expect.any(Array)); }); it('should handle subscriptions correctly', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); const listener = jest.fn(); const unsubscribe = obs.subscribe(listener); obs.set('value1'); unsubscribe(); obs.set('value2'); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith('value1', expect.any(Array)); }); it('should handle error emissions', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); const errorHandler = jest.fn(); obs.subscribe(undefined, errorHandler); const error = new Error('Test error'); obs.emitError(error); expect(errorHandler).toHaveBeenCalledWith(error, expect.any(Array)); }); it('should handle completion', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); const completeHandler = jest.fn(); obs.subscribe(undefined, undefined, completeHandler); obs.emitComplete(); expect(completeHandler).toHaveBeenCalledWith(expect.any(Array)); }); }); describe('Key management', () => { it('should use different keys for different observables', () => { const obs1 = (0, persistent_observable_1.createPersistentObservable)({ key: 'key1', storage: mockStorage, initialValue: 'value1', }); const obs2 = (0, persistent_observable_1.createPersistentObservable)({ key: 'key2', storage: mockStorage, initialValue: 'value2', }); obs1.set('new1'); obs2.set('new2'); expect(mockStorage.setItem).toHaveBeenCalledWith('key1', JSON.stringify('new1')); expect(mockStorage.setItem).toHaveBeenCalledWith('key2', JSON.stringify('new2')); }); it('should handle key collisions', () => { const obs1 = (0, persistent_observable_1.createPersistentObservable)({ key: 'same-key', storage: mockStorage, initialValue: 'value1', }); const obs2 = (0, persistent_observable_1.createPersistentObservable)({ key: 'same-key', storage: mockStorage, initialValue: 'value2', }); obs1.set('new1'); expect(obs2.get()).toBe('new1'); // Should share storage }); }); describe('Performance considerations', () => { it('should not save on silent set', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: mockStorage, initialValue: 'default', }); obs.setSilent('silent value'); expect(mockStorage.setItem).not.toHaveBeenCalled(); expect(obs.get()).toBe('silent value'); }); it('should handle large objects efficiently', () => { const largeObject = { data: Array.from({ length: 1000 }, (_, i) => ({ id: i, value: `item-${i}`, })), metadata: { timestamp: Date.now(), version: '1.0' }, }; const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'large-key', storage: mockStorage, initialValue: largeObject, }); expect(() => obs.set(largeObject)).not.toThrow(); expect(mockStorage.setItem).toHaveBeenCalledWith('large-key', JSON.stringify(largeObject)); }); }); describe('Edge cases', () => { it('should handle circular references gracefully', () => { const circular = { name: 'test' }; circular.self = circular; const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'circular-key', storage: mockStorage, initialValue: 'default', }); // Should not throw when setting circular reference expect(() => obs.set(circular)).not.toThrow(); }); it('should handle functions in objects', () => { const objWithFunction = { name: 'test', method: () => 'hello', }; const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'function-key', storage: mockStorage, initialValue: 'default', }); obs.set(objWithFunction); // Functions should be lost in JSON serialization const saved = JSON.parse(mockStorage.setItem.mock.calls[0][1]); expect(saved.method).toBeUndefined(); }); it('should handle undefined storage', () => { const obs = (0, persistent_observable_1.createPersistentObservable)({ key: 'test-key', storage: undefined, initialValue: 'default', }); expect(() => obs.set('new value')).not.toThrow(); expect(obs.get()).toBe('new value'); }); }); });