UNPKG

react-native-nami-sdk

Version:

React Native SDK for Nami - No-code paywall and onboarding flows with A/B testing.

98 lines (86 loc) 3.89 kB
// Mock react-native BEFORE importing the SDK — `../Nami` resolves // the native module via TurboModuleRegistry at import time. const reset = jest.fn().mockResolvedValue(undefined); // The wrapper reads `result?.success` (see Nami.ts), so the mock must // return that shape — not a plain boolean. const configure = jest.fn().mockResolvedValue({ success: true }); const sdkConfigured = jest.fn().mockResolvedValue(false); const sdkVersion = jest.fn().mockResolvedValue('test'); jest.mock('react-native', () => ({ TurboModuleRegistry: { getEnforcing: jest.fn(() => ({ reset, configure, sdkConfigured, sdkVersion, })), }, NativeModules: { RNNami: { reset, configure, sdkConfigured, sdkVersion }, }, })); import { Nami } from '../Nami'; describe('Nami.reset (React Native bridge)', () => { beforeEach(() => { reset.mockClear(); configure.mockClear(); }); it('exposes reset() as an async function on the JS surface', () => { expect(typeof Nami.reset).toBe('function'); }); it('delegates to the native RNNami.reset() turbomodule method', async () => { await Nami.reset(); expect(reset).toHaveBeenCalledTimes(1); }); it('returns a promise that resolves once the native call settles', async () => { const result = Nami.reset(); expect(result).toBeInstanceOf(Promise); await expect(result).resolves.toBeUndefined(); }); it('propagates native bridge rejections to the JS caller', async () => { const err = new Error('native reset failed'); reset.mockRejectedValueOnce(err); await expect(Nami.reset()).rejects.toThrow('native reset failed'); }); // NAM-1086: pre-fix, calling Nami.reset() before Nami.configure() on // Android crashed inside `NamiEntitlementManager`'s class-init block, // which read `Nami.refs.context` (a `lateinit`). The native bridge's // `try { Nami.reset(...) } catch (e: Throwable) { promise.reject(...) }` // turned that into a JS-observable `ExceptionInInitializerError` // rejection of the `Nami.reset()` promise. These tests pin the JS-side // contract: regardless of order or repetition, the bridge invocation // must resolve cleanly and the host app must not see a rejection. describe('order-of-calls (NAM-1086)', () => { it('does not reject when called before configure', async () => { // No prior `Nami.configure()` — simulates the cold-start path the // RN host app exercises when wiping persisted state on launch. await expect(Nami.reset()).resolves.toBeUndefined(); expect(reset).toHaveBeenCalledTimes(1); expect(configure).not.toHaveBeenCalled(); }); it('does not reject when called after configure', async () => { await Nami.configure({} as any); await expect(Nami.reset()).resolves.toBeUndefined(); expect(configure).toHaveBeenCalledTimes(1); expect(reset).toHaveBeenCalledTimes(1); }); it('is idempotent across repeated cold calls', async () => { // The host app may bind `Nami.reset()` to a "Clear Data" button // that the user can tap multiple times in a row — each tap must // resolve cleanly. await expect(Nami.reset()).resolves.toBeUndefined(); await expect(Nami.reset()).resolves.toBeUndefined(); await expect(Nami.reset()).resolves.toBeUndefined(); expect(reset).toHaveBeenCalledTimes(3); }); it('does not reject when interleaved with configure', async () => { // Realistic flow: reset cold → configure → reset again to wipe // post-configure state. Each step must resolve. await expect(Nami.reset()).resolves.toBeUndefined(); await expect(Nami.configure({} as any)).resolves.toBe(true); await expect(Nami.reset()).resolves.toBeUndefined(); expect(reset).toHaveBeenCalledTimes(2); expect(configure).toHaveBeenCalledTimes(1); }); }); });