UNPKG

@idiosync/react-observable

Version:

State management control layer for React projects

226 lines (225 loc) 13.7 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_observable_value_1 = require("./use-observable-value"); const observable_1 = require("../factories/observable"); const context_1 = require("../store/context"); // Test component that uses the hook const TestComponent = ({ initialise }) => { const value = (0, use_observable_value_1.useObservableValue)(initialise); return react_1.default.createElement("div", { "data-testid": "value" }, JSON.stringify(value)); }; describe('useObservableValue', () => { 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, wrapObservable }) => 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, wrapObservable }) => 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, wrapObservable }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('{"name":"John","age":25,"active":true}'); }); it('should handle arrays', () => { const obs = (0, observable_1.createObservable)({ initialValue: [1, 2, 3, 'test'], }); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: ({ store, wrapObservable }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('[1,2,3,"test"]'); }); }); 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({ store: expect.any(Object), wrapObservable: expect.any(Function), }); }); it('should allow accessing store observables', () => { const TestStoreComponent = () => { const value = (0, use_observable_value_1.useObservableValue)(({ store, wrapObservable }) => { 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('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, wrapObservable }) => 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, wrapObservable }) => 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_observable_value_1.useObservableValue)(({ store, wrapObservable }) => obs1); const value2 = (0, use_observable_value_1.useObservableValue)(({ store, wrapObservable }) => 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('Observable wrapping', () => { it('should provide wrapObservable function', () => { const mockInitialise = jest.fn(); (0, react_2.render)(react_1.default.createElement(context_1.ReactObservableProvider, null, react_1.default.createElement(TestComponent, { initialise: mockInitialise }))); const { wrapObservable } = mockInitialise.mock.calls[0][0]; expect(typeof wrapObservable).toBe('function'); }); it('should wrap observable with subscription tracking', () => { const obs = (0, observable_1.createObservable)({ initialValue: 'test' }); let wrappedObs; const TestWrapComponent = () => { const value = (0, use_observable_value_1.useObservableValue)(({ store, wrapObservable }) => { wrappedObs = wrapObservable(obs); return wrappedObs; }); 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(TestWrapComponent, null))); expect(wrappedObs).toBeDefined(); expect(wrappedObs.get()).toBe('test'); }); }); 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_observable_value_1.useObservableValue)(({ store, wrapObservable }) => 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_observable_value_1.useObservableValue)(({ store, wrapObservable }) => 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('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, wrapObservable }) => 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('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, wrapObservable }) => 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, wrapObservable }) => 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, wrapObservable }) => 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, wrapObservable }) => 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, wrapObservable }) => obs }))); expect(react_2.screen.getByTestId('value')).toHaveTextContent('"test"'); }); }); });