UNPKG

@idiosync/react-observable

Version:

State management control layer for React projects

279 lines (278 loc) 17.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = __importDefault(require("react")); const react_2 = require("@testing-library/react"); const use_effect_stream_1 = require("./use-effect-stream"); const observable_1 = require("../factories/observable"); const context_1 = require("../store/context"); // Test component that uses the hook const TestComponent = ({ initialise, deps, }) => { const value = (0, use_effect_stream_1.useEffectStream)(initialise, deps); return react_1.default.createElement("div", { "data-testid": "value" }, JSON.stringify(value)); }; describe('useEffectStream', () => { describe('Basic functionality', () => { it('should return observable value', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'test value' }); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('"test value"'); }); it('should update when observable changes', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('"initial"'); (0, react_2.act)(() => { obs.set('updated'); }); expect(react_2.screen.getByTestId('value')).toHaveTextContent('"updated"'); }); it('should handle complex objects', () => { const obs = (0, observable_1.createObservable)({ initialValue: { name: 'John', age: 25, active: true }, }); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('{"name":"John","age":25,"active":true}'); }); }); describe('Dependencies', () => { it('should re-initialize when dependencies change', () => { const mockInitialise = jest.fn(({ $, store }) => (0, observable_1.createObservable)({ initialValue: 'default' })); const { rerender } = (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: mockInitialise, deps: ['dep1'] }))); expect(mockInitialise).toHaveBeenCalledTimes(1); rerender(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: mockInitialise, deps: ['dep2'] }))); expect(mockInitialise).toHaveBeenCalledTimes(2); }); it('should not re-initialize when dependencies are the same', () => { const mockInitialise = jest.fn(({ $, store }) => (0, observable_1.createObservable)({ initialValue: 'default' })); const { rerender } = (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: mockInitialise, deps: ['same'] }))); expect(mockInitialise).toHaveBeenCalledTimes(1); rerender(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: mockInitialise, deps: ['same'] }))); expect(mockInitialise).toHaveBeenCalledTimes(1); }); it('should handle undefined dependencies', () => { const mockInitialise = jest.fn(({ $, store }) => (0, observable_1.createObservable)({ initialValue: 'default' })); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: mockInitialise }))); expect(mockInitialise).toHaveBeenCalledTimes(1); }); }); describe('Store integration', () => { it('should provide store to initialise function', () => { const mockInitialise = jest.fn(); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: mockInitialise }))); expect(mockInitialise).toHaveBeenCalledWith({ $: expect.any(Object), store: expect.any(Object), }); }); it('should allow accessing store observables', () => { const TestStoreComponent = () => { const value = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => { var _a; return (((_a = store.test) === null || _a === void 0 ? void 0 : _a.value$) || (0, observable_1.createObservable)({ initialValue: 'default' })); }); return react_1.default.createElement("div", { "data-testid": "value" }, value); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestStoreComponent, null))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('default'); }); }); describe('Stream operations', () => { it('should handle stream operations', () => { const obs = (0, observable_1.createObservable)({ initialValue: 5 }); const TestStreamComponent = () => { const value = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => { return obs.stream((val) => val * 2); }); return react_1.default.createElement("div", { "data-testid": "value" }, value); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestStreamComponent, null))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('10'); }); it('should handle async stream operations', async () => { const obs = (0, observable_1.createObservable)({ initialValue: 5 }); const TestAsyncStreamComponent = () => { const value = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => { return obs.streamAsync(async (val) => { await new Promise((resolve) => setTimeout(resolve, 10)); return val * 2; }); }); return react_1.default.createElement("div", { "data-testid": "value" }, value); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestAsyncStreamComponent, null))); // Wait for async operation await (0, react_2.act)(async () => { await new Promise((resolve) => setTimeout(resolve, 20)); }); expect(react_2.screen.getByTestId('value')).toHaveTextContent('10'); }); it('should handle combineLatestFrom', () => { const obs1 = (0, observable_1.createObservable)({ initialValue: 'a' }); const obs2 = (0, observable_1.createObservable)({ initialValue: 1 }); const TestCombineComponent = () => { const value = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => { return obs1.combineLatestFrom(obs2); }); return react_1.default.createElement("div", { "data-testid": "value" }, JSON.stringify(value)); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestCombineComponent, null))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('["a",1]'); }); it('should handle withLatestFrom', () => { const obs1 = (0, observable_1.createObservable)({ initialValue: 'a' }); const obs2 = (0, observable_1.createObservable)({ initialValue: 1 }); const TestWithLatestComponent = () => { const value = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => { return obs1.withLatestFrom(obs2); }); return react_1.default.createElement("div", { "data-testid": "value" }, JSON.stringify(value)); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestWithLatestComponent, null))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('["a",1]'); }); }); describe('Subscription management', () => { it('should subscribe to observable on mount', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const subscribeSpy = jest.spyOn(obs, 'subscribe'); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(subscribeSpy).toHaveBeenCalled(); }); it('should unsubscribe on unmount', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const unsubscribeSpy = jest.fn(); jest.spyOn(obs, 'subscribe').mockReturnValue(unsubscribeSpy); const { unmount } = (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); unmount(); expect(unsubscribeSpy).toHaveBeenCalled(); }); it('should handle multiple subscriptions', () => { const obs1 = (0, observable_1.createObservable)({ initialValue: 'obs1' }); const obs2 = (0, observable_1.createObservable)({ initialValue: 'obs2' }); const MultiObsComponent = () => { const value1 = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => obs1); const value2 = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => obs2); return (react_1.default.createElement("div", null, react_1.default.createElement("div", { "data-testid": "value1" }, value1), react_1.default.createElement("div", { "data-testid": "value2" }, value2))); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(MultiObsComponent, null))); expect(react_2.screen.getByTestId('value1')).toHaveTextContent('obs1'); expect(react_2.screen.getByTestId('value2')).toHaveTextContent('obs2'); }); }); describe('Error handling', () => { it('should handle observable errors gracefully', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); (0, react_2.act)(() => { obs.emitError(new Error('Test error')); }); // Should not crash the component expect(react_2.screen.getByTestId('value')).toBeInTheDocument(); consoleSpy.mockRestore(); }); it('should handle initialise function errors', () => { const errorInitialise = () => { throw new Error('Initialise error'); }; const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); expect(() => { (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: errorInitialise }))); }).toThrow('Initialise error'); consoleSpy.mockRestore(); }); }); describe('Performance and optimization', () => { it('should not re-render when observable value is the same', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'same' }); const renderSpy = jest.fn(); const TestRenderComponent = () => { renderSpy(); const value = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => obs); return react_1.default.createElement("div", { "data-testid": "value" }, value); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestRenderComponent, null))); const initialRenderCount = renderSpy.mock.calls.length; (0, react_2.act)(() => { obs.set('same'); // Same value }); expect(renderSpy.mock.calls.length).toBe(initialRenderCount); }); it('should re-render when observable value changes', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'initial' }); const renderSpy = jest.fn(); const TestRenderComponent = () => { renderSpy(); const value = (0, use_effect_stream_1.useEffectStream)(({ $, store }) => obs); return react_1.default.createElement("div", { "data-testid": "value" }, value); }; (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestRenderComponent, null))); const initialRenderCount = renderSpy.mock.calls.length; (0, react_2.act)(() => { obs.set('updated'); // Different value }); expect(renderSpy.mock.calls.length).toBeGreaterThan(initialRenderCount); }); }); describe('Edge cases', () => { it('should handle undefined observable values', () => { const obs = (0, observable_1.createObservable)(); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('null'); }); it('should handle null observable values', () => { const obs = (0, observable_1.createObservable)({ initialValue: null }); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('null'); }); it('should handle function initial values', () => { const obs = (0, observable_1.createObservable)({ initialValue: () => 'function value' }); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('"function value"'); }); }); describe('Provider context', () => { it('should work without provider (fallback behavior)', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'test' }); // This should not crash even without provider expect(() => { (0, react_2.render)(react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs })); }).not.toThrow(); }); it('should use provider context when available', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'test' }); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ $, store }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('"test"'); }); }); });