// Mock the heap-javascript-bridge-support module const mockCreateLogger = jest.fn(); const mockCreateHeap = jest.fn(); const mockCreateBridgeInvocationHandler = jest.fn(); const mockIsHeapSDKInvocation = jest.fn(); const mockSourceManager = jest.fn(); const mockLogger = { log: jest.fn(), error: jest.fn(), warn: jest.fn(), info: jest.fn(), }; const mockHeapObject = { track: jest.fn(), identify: jest.fn(), addEventProperties: jest.fn(), logToConsole: jest.fn(), }; const mockBridgeInvocationHandler = jest.fn(); jest.mock('@heap/heap-javascript-bridge-support', () => ({ createLogger: mockCreateLogger, createHeap: mockCreateHeap, createBridgeInvocationHandler: mockCreateBridgeInvocationHandler, isHeapSDKInvocation: mockIsHeapSDKInvocation, SourceManager: mockSourceManager, })); jest.mock('../../heap/version', () => ({ version: '0.8.4', })); const mockNativeEventEmitter = jest.fn(); const mockAddListener = jest.fn(); mockNativeEventEmitter.mockImplementation(() => ({ addListener: mockAddListener, })); describe('heap', () => { let createReactNativeHeap: ( HeapReactNativeBridge: unknown, Platform: unknown, NativeEventEmitter: unknown ) => [unknown, unknown]; beforeAll(() => { const module = require('../../heap/heap'); createReactNativeHeap = module.createReactNativeHeap; }); beforeEach(() => { jest.clearAllMocks(); mockCreateLogger.mockReturnValue(mockLogger); mockCreateHeap.mockReturnValue(mockHeapObject); mockCreateBridgeInvocationHandler.mockReturnValue(mockBridgeInvocationHandler); mockSourceManager.mockImplementation(() => ({ register: jest.fn(), unregister: jest.fn(), })); }); afterEach(() => { jest.restoreAllMocks(); }); describe('createReactNativeHeap', () => { describe('with valid HeapReactNativeBridge', () => { it('should create heap and logger with bridge handleInvocation', () => { const mockHandleInvocation = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const [heap, logger] = createReactNativeHeap( mockHeapBridge, mockPlatform, mockNativeEventEmitter ); expect(mockCreateLogger).toHaveBeenCalledWith(mockHandleInvocation); expect(mockSourceManager).toHaveBeenCalledWith(mockLogger); expect(mockCreateHeap).toHaveBeenCalledWith( mockHandleInvocation, mockLogger, { name: 'react_native_bridge', version: '0.8.4', platform: 'React Native 0.72.4', }, expect.any(Object) ); expect(heap).toBe(mockHeapObject); expect(logger).toBe(mockLogger); }); it('should set up event listener when handleResult is available (old architecture)', () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, // No invocation function = old architecture }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); expect(mockNativeEventEmitter).toHaveBeenCalledWith(mockHeapBridge); expect(mockCreateBridgeInvocationHandler).toHaveBeenCalledWith( expect.any(Object), mockLogger ); expect(mockAddListener).toHaveBeenCalledWith('Invocation', expect.any(Function)); }); it('should set up event listener when handleResult is available (new architecture)', () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockInvocationCallback = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, invocation: mockInvocationCallback, // Function = new architecture }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); // Should not use NativeEventEmitter for new architecture expect(mockNativeEventEmitter).not.toHaveBeenCalled(); expect(mockCreateBridgeInvocationHandler).toHaveBeenCalledWith( expect.any(Object), mockLogger ); expect(mockInvocationCallback).toHaveBeenCalledWith(expect.any(Function)); }); it('should handle invocation events correctly (old architecture)', async () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const mockEvent = { type: 'heapSDK', data: 'test' }; const mockResult = { callbackId: '123', data: 'response', error: undefined }; mockIsHeapSDKInvocation.mockReturnValue(true); mockBridgeInvocationHandler.mockResolvedValue(mockResult); createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); const eventListener = mockAddListener.mock.calls[0][1]; await eventListener(mockEvent); expect(mockIsHeapSDKInvocation).toHaveBeenCalledWith(mockEvent); expect(mockBridgeInvocationHandler).toHaveBeenCalledWith(mockEvent); expect(mockHandleResult).toHaveBeenCalledWith('123', 'response', undefined); }); it('should handle invocation events correctly (new architecture)', async () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockInvocationCallback = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, invocation: mockInvocationCallback, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const mockEvent = { type: 'heapSDK', data: 'test' }; const mockResult = { callbackId: '123', data: 'response', error: undefined }; mockIsHeapSDKInvocation.mockReturnValue(true); mockBridgeInvocationHandler.mockResolvedValue(mockResult); createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); // Get the callback passed to invocation const invocationCallback = mockInvocationCallback.mock.calls[0][0]; await invocationCallback(mockEvent); expect(mockIsHeapSDKInvocation).toHaveBeenCalledWith(mockEvent); expect(mockBridgeInvocationHandler).toHaveBeenCalledWith(mockEvent); expect(mockHandleResult).toHaveBeenCalledWith('123', 'response', undefined); }); it('should not call handleResult when bridgeInvocationHandler returns null (old architecture)', async () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const mockEvent = { type: 'heapSDK', data: 'test' }; mockIsHeapSDKInvocation.mockReturnValue(true); mockBridgeInvocationHandler.mockResolvedValue(null); createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); const eventListener = mockAddListener.mock.calls[0][1]; await eventListener(mockEvent); expect(mockHandleResult).not.toHaveBeenCalled(); }); it('should not call handleResult when bridgeInvocationHandler returns null (new architecture)', async () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockInvocationCallback = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, invocation: mockInvocationCallback, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const mockEvent = { type: 'heapSDK', data: 'test' }; mockIsHeapSDKInvocation.mockReturnValue(true); mockBridgeInvocationHandler.mockResolvedValue(null); createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); const invocationCallback = mockInvocationCallback.mock.calls[0][0]; await invocationCallback(mockEvent); expect(mockHandleResult).not.toHaveBeenCalled(); }); it('should ignore non-heap SDK invocations (old architecture)', async () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const mockEvent = { type: 'other', data: 'test' }; mockIsHeapSDKInvocation.mockReturnValue(false); createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); const eventListener = mockAddListener.mock.calls[0][1]; await eventListener(mockEvent); expect(mockBridgeInvocationHandler).not.toHaveBeenCalled(); expect(mockHandleResult).not.toHaveBeenCalled(); }); it('should ignore non-heap SDK invocations (new architecture)', async () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockInvocationCallback = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, invocation: mockInvocationCallback, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const mockEvent = { type: 'other', data: 'test' }; mockIsHeapSDKInvocation.mockReturnValue(false); createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); const invocationCallback = mockInvocationCallback.mock.calls[0][0]; await invocationCallback(mockEvent); expect(mockBridgeInvocationHandler).not.toHaveBeenCalled(); expect(mockHandleResult).not.toHaveBeenCalled(); }); }); describe('without HeapReactNativeBridge (fallback to noop)', () => { it('should create heap with noop bridge when HeapReactNativeBridge is undefined', () => { const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); const [heap, logger] = createReactNativeHeap( undefined, mockPlatform, mockNativeEventEmitter ); expect(mockCreateLogger).toHaveBeenCalledWith(expect.any(Function)); expect(heap).toBe(mockHeapObject); expect(logger).toBe(mockLogger); const noopHandler = mockCreateLogger.mock.calls[0][0]; const result1 = noopHandler(); const result2 = noopHandler(); expect(result1).resolves.toBeNull(); expect(result2).resolves.toBeNull(); expect(consoleSpy).toHaveBeenCalledTimes(1); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining('Failed to find a suitable bridge for CSQ') ); consoleSpy.mockRestore(); }); it('should create heap with noop bridge when handleInvocation is missing', () => { const mockHeapBridge = {}; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 72, patch: 4 }, }, }; const [heap, logger] = createReactNativeHeap( mockHeapBridge, mockPlatform, mockNativeEventEmitter ); expect(mockCreateLogger).toHaveBeenCalledWith(expect.any(Function)); expect(heap).toBe(mockHeapObject); expect(logger).toBe(mockLogger); }); }); describe('React Native version extraction', () => { it('should extract React Native version correctly', () => { const mockHandleInvocation = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, }; const mockPlatform = { constants: { reactNativeVersion: { major: 0, minor: 70, patch: 8 }, }, }; createReactNativeHeap(mockHeapBridge, mockPlatform, mockNativeEventEmitter); expect(mockCreateHeap).toHaveBeenCalledWith( mockHandleInvocation, mockLogger, { name: 'react_native_bridge', version: '0.8.4', platform: 'React Native 0.70.8', }, expect.any(Object) ); }); }); describe('error handling', () => { it('should propagate errors from event listener', async () => { const mockHandleInvocation = jest.fn(); const mockHandleResult = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, handleResult: mockHandleResult, }; const mockEvent = { type: 'heapSDK', data: 'test' }; mockIsHeapSDKInvocation.mockReturnValue(true); mockBridgeInvocationHandler.mockRejectedValue(new Error('Handler error')); createReactNativeHeap(mockHeapBridge, undefined, mockNativeEventEmitter); const eventListener = mockAddListener.mock.calls[0][1]; await expect(eventListener(mockEvent)).rejects.toThrow('Handler error'); expect(mockHandleResult).not.toHaveBeenCalled(); }); }); describe('source info configuration', () => { it('should always use correct source info name and version', () => { const mockHandleInvocation = jest.fn(); const mockHeapBridge = { handleInvocation: mockHandleInvocation, }; createReactNativeHeap(mockHeapBridge, undefined, mockNativeEventEmitter); const sourceInfo = mockCreateHeap.mock.calls[0][2]; expect(sourceInfo.name).toBe('react_native_bridge'); expect(sourceInfo.version).toBe('0.8.4'); }); }); }); });