import { applyNative } from '@jolibox/native-bridge'; import { ISubscriptionPlanData } from '@/native/subscription/type'; jest.mock('@jolibox/native-bridge', () => ({ applyNative: jest.fn(), onNative: jest.fn(), invokeNative: jest.fn() })); const mockApplyNative = applyNative as jest.Mock; /** * Replicate the core logic of getSpinSubscriptionPrice from payment.ts * to test it in isolation without triggering the full module import chain. */ async function getSpinSubscriptionPriceImpl(params: { appStoreProductId: string }) { const { data } = await applyNative('requestSubscriptionPlansAsync', { appStoreProductIds: [params.appStoreProductId], type: 'SPIN' }); if (!data) { throw new Error('[JoliboxSDK]: get spin subscription price failed'); } const plans = Object.values(data as Record).map((plan) => ({ basePlanId: plan.basePlanId, productId: plan.productId, formattedPrice: plan.pricing.formattedPrice, originalPrice: plan.pricing.originalPrice, trialDay: plan.trial?.duration ?? 0 })); return { plans }; } describe('getSpinSubscriptionPrice', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should call applyNative with type SPIN and return formatted plans', async () => { mockApplyNative.mockResolvedValue({ data: { 'spin-weekly': { basePlanId: 'spin-weekly', productId: 'jolibox_app_spin_sub', pricing: { formattedPrice: '$1.99', originalPrice: '$1.99', discountPercentage: null }, trial: { duration: 3, description: '' } } } }); const result = await getSpinSubscriptionPriceImpl({ appStoreProductId: 'jolibox_app_spin_sub' }); expect(mockApplyNative).toHaveBeenCalledWith('requestSubscriptionPlansAsync', { appStoreProductIds: ['jolibox_app_spin_sub'], type: 'SPIN' }); expect(result).toEqual({ plans: [ { basePlanId: 'spin-weekly', productId: 'jolibox_app_spin_sub', formattedPrice: '$1.99', originalPrice: '$1.99', trialDay: 3 } ] }); }); it('should handle multiple plans', async () => { mockApplyNative.mockResolvedValue({ data: { 'spin-weekly': { basePlanId: 'spin-weekly', productId: 'jolibox_app_spin_sub', pricing: { formattedPrice: '$1.99', originalPrice: '$1.99' }, trial: { duration: 3 } }, 'spin-monthly': { basePlanId: 'spin-monthly', productId: 'jolibox_app_spin_sub', pricing: { formattedPrice: '$4.99', originalPrice: '$4.99' }, trial: { duration: 7 } } } }); const result = await getSpinSubscriptionPriceImpl({ appStoreProductId: 'jolibox_app_spin_sub' }); expect(result.plans).toHaveLength(2); expect(result.plans).toEqual( expect.arrayContaining([ expect.objectContaining({ basePlanId: 'spin-weekly', formattedPrice: '$1.99' }), expect.objectContaining({ basePlanId: 'spin-monthly', formattedPrice: '$4.99' }) ]) ); }); it('should default trialDay to 0 when trial is undefined', async () => { mockApplyNative.mockResolvedValue({ data: { 'spin-weekly': { basePlanId: 'spin-weekly', productId: 'jolibox_app_spin_sub', pricing: { formattedPrice: '$1.99', originalPrice: '$1.99' } } } }); const result = await getSpinSubscriptionPriceImpl({ appStoreProductId: 'jolibox_app_spin_sub' }); expect(result.plans[0].trialDay).toBe(0); }); it('should throw error when native returns no data', async () => { mockApplyNative.mockResolvedValue({ data: null }); await expect(getSpinSubscriptionPriceImpl({ appStoreProductId: 'jolibox_app_spin_sub' })).rejects.toThrow( 'get spin subscription price failed' ); }); it('should throw error when native returns undefined data', async () => { mockApplyNative.mockResolvedValue({}); await expect(getSpinSubscriptionPriceImpl({ appStoreProductId: 'jolibox_app_spin_sub' })).rejects.toThrow( 'get spin subscription price failed' ); }); it('should throw error when native call rejects', async () => { mockApplyNative.mockRejectedValue(new Error('Native bridge unavailable')); await expect(getSpinSubscriptionPriceImpl({ appStoreProductId: 'jolibox_app_spin_sub' })).rejects.toThrow( 'Native bridge unavailable' ); }); });