UNPKG

@idiosync/react-observable

Version:

State management control layer for React projects

313 lines (312 loc) 14.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const observable_1 = require("./observable"); describe('createObservable', () => { describe('Basic functionality', () => { it('should create an observable with initial value', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'test' }); expect(obs.get()).toBe('test'); }); it('should create an observable with function initial value', () => { const obs = (0, observable_1.createObservable)({ initialValue: () => 'test' }); expect(obs.get()).toBe('test'); }); it('should create an observable with undefined initial value', () => { const obs = (0, observable_1.createObservable)(); expect(obs.get()).toBeUndefined(); }); it('should have unique ID', () => { const obs1 = (0, observable_1.createObservable)(); const obs2 = (0, observable_1.createObservable)(); expect(obs1.getId()).not.toBe(obs2.getId()); }); it('should have default name based on ID', () => { const obs = (0, observable_1.createObservable)(); expect(obs.getName()).toBe(obs.getId()); }); it('should allow setting custom name', () => { const obs = (0, observable_1.createObservable)({ name: 'custom-name' }); expect(obs.getName()).toBe('custom-name'); }); it('should allow changing name', () => { const obs = (0, observable_1.createObservable)({ name: 'old-name' }); obs.setName('new-name'); expect(obs.getName()).toBe('new-name'); }); }); describe('Value setting and getting', () => { it('should set and get primitive values', () => { const obs = (0, observable_1.createObservable)({ initialValue: 0 }); obs.set(42); expect(obs.get()).toBe(42); }); it('should set and get object values', () => { const obs = (0, observable_1.createObservable)({ initialValue: { name: 'test' } }); const newValue = { name: 'updated', age: 25 }; obs.set(newValue); expect(obs.get()).toEqual(newValue); }); it('should set value using function', () => { const obs = (0, observable_1.createObservable)({ initialValue: 10 }); obs.set((current) => current + 5); expect(obs.get()).toBe(15); }); it('should not emit when value is the same', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'test' }); const listener = jest.fn(); obs.subscribe(listener); obs.set('test'); // Same value expect(listener).not.toHaveBeenCalled(); }); it('should not emit when equality function returns true', () => { const obs = (0, observable_1.createObservable)({ initialValue: { id: 1, name: 'test' }, equalityFn: (a, b) => a.id === b.id, }); const listener = jest.fn(); obs.subscribe(listener); obs.set({ id: 1, name: 'different' }); // Same ID expect(listener).not.toHaveBeenCalled(); }); it('should emit when equality function returns false', () => { const obs = (0, observable_1.createObservable)({ initialValue: { id: 1, name: 'test' }, equalityFn: (a, b) => a.id === b.id, }); const listener = jest.fn(); obs.subscribe(listener); obs.set({ id: 2, name: 'test' }); // Different ID expect(listener).toHaveBeenCalledWith({ id: 2, name: 'test' }, expect.any(Array)); }); }); describe('Subscription and emission', () => { it('should notify subscribers when value changes', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener = jest.fn(); obs.subscribe(listener); obs.set('new value'); expect(listener).toHaveBeenCalledWith('new value', expect.any(Array)); }); it('should notify multiple subscribers', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener1 = jest.fn(); const listener2 = jest.fn(); obs.subscribe(listener1); obs.subscribe(listener2); obs.set('new value'); expect(listener1).toHaveBeenCalledWith('new value', expect.any(Array)); expect(listener2).toHaveBeenCalledWith('new value', expect.any(Array)); }); it('should allow unsubscribing', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener = jest.fn(); const unsubscribe = obs.subscribe(listener); unsubscribe(); obs.set('new value'); expect(listener).not.toHaveBeenCalled(); }); it('should handle subscribeOnce correctly', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener = jest.fn(); obs.subscribeOnce(listener); obs.set('first'); obs.set('second'); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith('first', expect.any(Array)); }); it('should handle subscribeWithValue correctly', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener = jest.fn(); obs.subscribeWithValue(listener); expect(listener).toHaveBeenCalledWith('initial'); obs.set('new value'); expect(listener).toHaveBeenCalledWith('new value', expect.any(Array)); }); it('should track emit count', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); expect(obs.getEmitCount()).toBe(0); obs.set('first'); expect(obs.getEmitCount()).toBe(1); obs.set('second'); expect(obs.getEmitCount()).toBe(2); }); }); describe('Error handling', () => { it('should emit errors to error subscribers', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); 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 errors in catchError', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const errorHandler = jest.fn(); const errorObservable = obs.catchError(errorHandler); const error = new Error('Test error'); obs.emitError(error); expect(errorHandler).toHaveBeenCalledWith(error, 'initial', expect.any(Function)); }); it('should allow recovery from errors', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const errorObservable = obs.catchError((error, currentValue, setter) => { setter('recovered'); }); obs.emitError(new Error('Test error')); expect(obs.get()).toBe('recovered'); }); }); describe('Stream completion', () => { it('should emit completion to completion subscribers', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const completeHandler = jest.fn(); obs.subscribe(undefined, undefined, completeHandler); obs.emitComplete(); expect(completeHandler).toHaveBeenCalledWith(expect.any(Array)); }); it('should prevent further emissions after completion', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener = jest.fn(); obs.subscribe(listener); obs.emitComplete(); obs.set('should not emit'); expect(listener).not.toHaveBeenCalled(); }); }); describe('Stack tracking', () => { it('should include stack information in emissions', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener = jest.fn(); obs.subscribe(listener); obs.set('new value'); const callArgs = listener.mock.calls[0]; const stack = callArgs[1]; expect(stack).toBeInstanceOf(Array); expect(stack[0]).toHaveProperty('id'); expect(stack[0]).toHaveProperty('name'); expect(stack[0]).toHaveProperty('emitCount'); expect(stack[0]).toHaveProperty('isError'); }); it('should mark error stack items as errors', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const errorHandler = jest.fn(); obs.subscribe(undefined, errorHandler); obs.emitError(new Error('Test error')); const callArgs = errorHandler.mock.calls[0]; const stack = callArgs[1]; expect(stack[0].isError).toBe(true); }); }); describe('Stream operations', () => { it('should handle stream projection', () => { const obs = (0, observable_1.createObservable)({ initialValue: 5 }); const streamed = obs.stream((value) => value * 2); expect(streamed.get()).toBe(10); obs.set(10); expect(streamed.get()).toBe(20); }); it('should handle async stream projection', async () => { const obs = (0, observable_1.createObservable)({ initialValue: 5 }); const streamed = obs.streamAsync(async (value) => value * 2); // Wait for async operation await new Promise((resolve) => setTimeout(resolve, 0)); expect(streamed.get()).toBe(10); }); it('should handle tap operator', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const tapped = obs.tap((value) => { // Side effect }); expect(tapped.get()).toBe('initial'); expect(tapped).toBe(obs); // Should return same observable }); it('should handle delay operator', () => { jest.useFakeTimers(); const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const delayed = obs.delay(1000); const listener = jest.fn(); delayed.subscribe(listener); obs.set('new value'); expect(listener).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); expect(listener).toHaveBeenCalledWith('new value', expect.any(Array)); jest.useRealTimers(); }); it('should handle guard operator', () => { const obs = (0, observable_1.createObservable)({ initialValue: 5 }); const guarded = obs.guard((prev, next) => next > prev); const listener = jest.fn(); guarded.subscribe(listener); obs.set(3); // Should not emit (3 < 5) expect(listener).not.toHaveBeenCalled(); obs.set(7); // Should emit (7 > 5) expect(listener).toHaveBeenCalledWith(7, expect.any(Array)); }); }); describe('Combination operations', () => { it('should handle combineLatestFrom', () => { const obs1 = (0, observable_1.createObservable)({ initialValue: 'a' }); const obs2 = (0, observable_1.createObservable)({ initialValue: 1 }); const combined = obs1.combineLatestFrom(obs2); expect(combined.get()).toEqual(['a', 1]); obs1.set('b'); expect(combined.get()).toEqual(['b', 1]); obs2.set(2); expect(combined.get()).toEqual(['b', 2]); }); it('should handle withLatestFrom', () => { const obs1 = (0, observable_1.createObservable)({ initialValue: 'a' }); const obs2 = (0, observable_1.createObservable)({ initialValue: 1 }); const withLatest = obs1.withLatestFrom(obs2); expect(withLatest.get()).toEqual(['a', 1]); obs1.set('b'); expect(withLatest.get()).toEqual(['b', 1]); obs2.set(2); expect(withLatest.get()).toEqual(['b', 2]); }); }); describe('Map entries', () => { it('should map observable entries', () => { const obs = (0, observable_1.createObservable)({ initialValue: { name: 'test', age: 25 } }); const mapped = obs.mapEntries({ keys: ['name', 'age'] }); expect(mapped).toHaveProperty('name$'); expect(mapped).toHaveProperty('age$'); expect(mapped.name$.get()).toBe('test'); expect(mapped.age$.get()).toBe(25); }); it('should update mapped entries when source changes', () => { const obs = (0, observable_1.createObservable)({ initialValue: { name: 'test', age: 25 } }); const mapped = obs.mapEntries({ keys: ['name', 'age'] }); obs.set({ name: 'updated', age: 30 }); expect(mapped.name$.get()).toBe('updated'); expect(mapped.age$.get()).toBe(30); }); }); describe('Reset functionality', () => { it('should reset to initial value', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); obs.set('changed'); expect(obs.get()).toBe('changed'); obs.reset(); expect(obs.get()).toBe('initial'); }); it('should reset function initial value', () => { const obs = (0, observable_1.createObservable)({ initialValue: () => 'initial' }); obs.set('changed'); expect(obs.get()).toBe('changed'); obs.reset(); expect(obs.get()).toBe('initial'); }); }); describe('Silent operations', () => { it('should set value without emitting', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const listener = jest.fn(); obs.subscribe(listener); obs.setSilent('silent'); expect(obs.get()).toBe('silent'); expect(listener).not.toHaveBeenCalled(); }); }); });