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