@idiosync/react-observable
Version:
State management control layer for React projects
313 lines (312 loc) • 13.4 kB
JavaScript
;
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');
});
});
});