From f10e6c38a5ec450652a006d8d82fd45746a9ae80 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Mon, 30 Mar 2026 15:23:31 +0100 Subject: [PATCH 1/4] feat: replace interop with new arch code --- .../RNShopifyCheckoutSheetKit.podspec | 38 +++++++--------- .../checkout-sheet-kit/android/build.gradle | 9 +--- .../ShopifyCheckoutSheetKitModule.java | 45 ++++++++++++++----- .../ShopifyCheckoutSheetKitPackage.java | 41 ++++++++++++----- .../ios/ShopifyCheckoutSheetKit.mm | 28 +++++++++++- .../ios/ShopifyCheckoutSheetKit.swift | 8 ++++ .../@shopify/checkout-sheet-kit/package.json | 8 ++++ 7 files changed, 123 insertions(+), 54 deletions(-) diff --git a/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec b/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec index 092b4913..a677816b 100644 --- a/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec +++ b/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec @@ -4,8 +4,6 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json"))) folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' -fabric_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1' - Pod::Spec.new do |s| s.name = "RNShopifyCheckoutSheetKit" s.version = package["version"] @@ -23,25 +21,21 @@ Pod::Spec.new do |s| s.dependency "ShopifyCheckoutSheetKit", "~> 3.8.0" s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.8.0" - if fabric_enabled - # Use React Native's helper if available, otherwise add dependencies directly - if defined?(install_modules_dependencies) - install_modules_dependencies(s) - else - # Fallback: manually specify dependencies for New Architecture - s.dependency "React-Codegen" - s.dependency "RCT-Folly", :modular_headers => true - s.dependency "RCTRequired" - s.dependency "RCTTypeSafety" - s.dependency "ReactCommon/turbomodule/core" - end - - s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" - - s.pod_target_xcconfig = { - "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", - "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" - } + if defined?(install_modules_dependencies) + install_modules_dependencies(s) + else + s.dependency "React-Codegen" + s.dependency "RCT-Folly", :modular_headers => true + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" end + + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } end diff --git a/modules/@shopify/checkout-sheet-kit/android/build.gradle b/modules/@shopify/checkout-sheet-kit/android/build.gradle index 82b9a034..dd55f324 100644 --- a/modules/@shopify/checkout-sheet-kit/android/build.gradle +++ b/modules/@shopify/checkout-sheet-kit/android/build.gradle @@ -9,15 +9,8 @@ buildscript { } } -def isNewArchitectureEnabled() { - return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" -} - apply plugin: "com.android.library" - -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} +apply plugin: "com.facebook.react" def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties[name]).toInteger() diff --git a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java b/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java index 53dd39e2..8c5c3196 100644 --- a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java +++ b/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java @@ -29,19 +29,19 @@ of this software and associated documentation files (the "Software"), to deal import androidx.annotation.NonNull; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; +import com.shopify.checkoutsheetkit.NativeShopifyCheckoutSheetKitSpec; import com.shopify.checkoutsheetkit.*; import java.util.HashMap; import java.util.Map; import java.util.Objects; -public class ShopifyCheckoutSheetKitModule extends ReactContextBaseJavaModule { - private static final String MODULE_NAME = "ShopifyCheckoutSheetKit"; +public class ShopifyCheckoutSheetKitModule extends NativeShopifyCheckoutSheetKitSpec { public static Configuration checkoutConfig = new Configuration(); @@ -62,14 +62,8 @@ public ShopifyCheckoutSheetKitModule(ReactApplicationContext reactContext) { }); } - @NonNull @Override - public String getName() { - return MODULE_NAME; - } - - @Override - public Map getConstants() { + protected Map getTypedExportedConstants() { final Map constants = new HashMap<>(); constants.put("version", ShopifyCheckoutSheetKit.version); return constants; @@ -81,7 +75,7 @@ public void addListener(String eventName) { } @ReactMethod - public void removeListeners(Integer count) { + public void removeListeners(double count) { // No-op but required for RN to register module } @@ -172,7 +166,34 @@ public void setConfig(ReadableMap config) { } @ReactMethod - public void initiateGeolocationRequest(Boolean allow) { + public void configureAcceleratedCheckouts( + String storefrontDomain, + String storefrontAccessToken, + String customerEmail, + String customerPhoneNumber, + String customerAccessToken, + String applePayMerchantIdentifier, + ReadableArray applyPayContactFields, + ReadableArray supportedShippingCountries, + Promise promise) { + // Accelerated checkouts not supported on Android + promise.resolve(false); + } + + @ReactMethod + public void isAcceleratedCheckoutAvailable(Promise promise) { + // Accelerated checkouts not supported on Android + promise.resolve(false); + } + + @ReactMethod + public void isApplePayAvailable(Promise promise) { + // Apple Pay not available on Android + promise.resolve(false); + } + + @ReactMethod + public void initiateGeolocationRequest(boolean allow) { if (checkoutEventProcessor != null) { checkoutEventProcessor.invokeGeolocationCallback(allow); } diff --git a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java b/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java index 5300fb63..f6cda161 100644 --- a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java +++ b/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java @@ -24,17 +24,21 @@ of this software and associated documentation files (the "Software"), to deal package com.shopify.reactnative.checkoutsheetkit; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import com.facebook.react.ReactPackage; +import com.facebook.react.TurboReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; import com.facebook.react.uimanager.ViewManager; -import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; -public class ShopifyCheckoutSheetKitPackage implements ReactPackage { +public class ShopifyCheckoutSheetKitPackage extends TurboReactPackage { @NonNull @Override @@ -42,15 +46,30 @@ public List createViewManagers(@NonNull ReactApplicationContext rea return Collections.emptyList(); } - @NonNull + @Nullable @Override - public List createNativeModules( - @NonNull ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - - modules.add(new ShopifyCheckoutSheetKitModule(reactContext)); - - return modules; + public NativeModule getModule(@NonNull String name, @NonNull ReactApplicationContext reactContext) { + if (name.equals(ShopifyCheckoutSheetKitModule.NAME)) { + return new ShopifyCheckoutSheetKitModule(reactContext); + } + return null; } + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return () -> { + final Map moduleInfos = new HashMap<>(); + moduleInfos.put( + ShopifyCheckoutSheetKitModule.NAME, + new ReactModuleInfo( + ShopifyCheckoutSheetKitModule.NAME, + ShopifyCheckoutSheetKitModule.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // isCxxModule + true // isTurboModule + )); + return moduleInfos; + }; + } } diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm index 5a3d7017..0655c3b1 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm @@ -24,8 +24,9 @@ of this software and associated documentation files (the "Software"), to deal #import #import +#import -@interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NSObject) +@interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NativeShopifyCheckoutSheetKitSpecBase) /** * Present checkout @@ -77,6 +78,31 @@ @interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NSObject) */ RCT_EXTERN_METHOD(isApplePayAvailable : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); +/** + * Respond to geolocation permission request + */ +RCT_EXTERN_METHOD(initiateGeolocationRequest : (BOOL)allow); + +@end + +// TurboModule registration. `RCTModuleProviders` (generated by codegen from +// `package.json → codegenConfig.ios.modulesProvider`) looks up classes that +// `respondsToSelector:@selector(getTurboModule:)`. Without this category, +// our Swift class would not appear in the providers map and JS calls would +// fall back to the legacy bridge — which has no methods declared here and +// no sync support. +@interface RCTShopifyCheckoutSheetKit (TurboModule) +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params; +@end + +@implementation RCTShopifyCheckoutSheetKit (TurboModule) +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared( + params); +} @end /** diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift index 25425e7c..200f505c 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift @@ -103,6 +103,10 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { ] } + @objc func getConstants() -> [AnyHashable: Any]! { + return constantsToExport() + } + static func getRootViewController() -> UIViewController? { return ( UIApplication.shared.connectedScenes @@ -302,6 +306,10 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { resolve(available) } + @objc func initiateGeolocationRequest(_ allow: Bool) { + // No-op on iOS — geolocation permission is handled natively + } + // MARK: - Private @available(iOS 16.0, *) diff --git a/modules/@shopify/checkout-sheet-kit/package.json b/modules/@shopify/checkout-sheet-kit/package.json index 76ebaa87..522d60d2 100644 --- a/modules/@shopify/checkout-sheet-kit/package.json +++ b/modules/@shopify/checkout-sheet-kit/package.json @@ -60,6 +60,14 @@ "jsSrcsDir": "src/specs", "android": { "javaPackageName": "com.shopify.checkoutsheetkit" + }, + "ios": { + "modulesProvider": { + "ShopifyCheckoutSheetKit": "RCTShopifyCheckoutSheetKit" + }, + "componentProvider": { + "RCTAcceleratedCheckoutButtons": "RCTAcceleratedCheckoutButtonsManager" + } } }, "react-native-builder-bob": { From 25562a4763910f2588c2cca9016c8fbc82ea4406 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Fri, 17 Apr 2026 09:22:14 +0100 Subject: [PATCH 2/4] feat!: convert getConfig and accelerated checkout APIs to sync Now that we're on TurboModules, the four promise-based methods (getConfig, configureAcceleratedCheckouts, isAcceleratedCheckoutAvailable, isApplePayAvailable) can use synchronous JSI dispatch. Their native implementations are pure in-memory reads or state writes, so the Promise wrapper was boilerplate that blocked readable call sites. BREAKING CHANGE: Consumers must drop `await` from these calls. - Spec: return ConfigurationResultSpec / boolean directly instead of Promise - iOS Swift: remove resolve/reject, return NSDictionary / NSNumber - Android: annotate with isBlockingSynchronousMethod = true, return directly - Consumer API in src/index.ts and src/context.tsx: drop async / await - Tests: replace mockResolvedValue with mockReturnValue, drop awaits - Sample app: drop awaits in Cart and SettingsScreen Co-Authored-By: Claude Opus 4.6 (1M context) --- __mocks__/react-native.ts | 7 +- .../ShopifyCheckoutSheetKitModule.java | 27 ++++---- .../ios/ShopifyCheckoutSheetKit.mm | 66 +++---------------- .../ios/ShopifyCheckoutSheetKit.swift | 40 ++++------- .../checkout-sheet-kit/src/context.tsx | 44 ++++++------- .../checkout-sheet-kit/src/index.d.ts | 6 +- .../@shopify/checkout-sheet-kit/src/index.ts | 60 +++++++++-------- .../specs/NativeShopifyCheckoutSheetKit.ts | 8 +-- .../checkout-sheet-kit/tests/context.test.tsx | 10 ++- .../checkout-sheet-kit/tests/index.test.ts | 66 ++++++++----------- .../ShopifyCheckoutSheetKitModuleTest.java | 30 +++------ sample/src/context/Cart.tsx | 4 +- sample/src/screens/SettingsScreen.tsx | 11 ++-- 13 files changed, 147 insertions(+), 232 deletions(-) diff --git a/__mocks__/react-native.ts b/__mocks__/react-native.ts index 69210799..3e9d71eb 100644 --- a/__mocks__/react-native.ts +++ b/__mocks__/react-native.ts @@ -54,13 +54,14 @@ const ShopifyCheckoutSheetKit = { present: jest.fn(), dismiss: jest.fn(), invalidateCache: jest.fn(), - getConfig: jest.fn(async () => exampleConfig), + getConfig: jest.fn(() => exampleConfig), setConfig: jest.fn(), addEventListener: jest.fn(), removeEventListeners: jest.fn(), initiateGeolocationRequest: jest.fn(), - configureAcceleratedCheckouts: jest.fn(), - isAcceleratedCheckoutAvailable: jest.fn(), + configureAcceleratedCheckouts: jest.fn(() => true), + isAcceleratedCheckoutAvailable: jest.fn(() => true), + isApplePayAvailable: jest.fn(() => true), addListener: jest.fn(), removeListeners: jest.fn(), }; diff --git a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java b/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java index 8c5c3196..03427892 100644 --- a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java +++ b/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java @@ -113,15 +113,15 @@ public void invalidateCache() { ShopifyCheckoutSheetKit.invalidate(); } - @ReactMethod - public void getConfig(Promise promise) { + @ReactMethod(isBlockingSynchronousMethod = true) + public WritableMap getConfig() { WritableMap resultConfig = Arguments.createMap(); resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled()); resultConfig.putString("colorScheme", colorSchemeToString(checkoutConfig.getColorScheme())); resultConfig.putString("logLevel", logLevelToString(checkoutConfig.getLogLevel())); - promise.resolve(resultConfig); + return resultConfig; } @ReactMethod @@ -165,8 +165,8 @@ public void setConfig(ReadableMap config) { }); } - @ReactMethod - public void configureAcceleratedCheckouts( + @ReactMethod(isBlockingSynchronousMethod = true) + public boolean configureAcceleratedCheckouts( String storefrontDomain, String storefrontAccessToken, String customerEmail, @@ -174,22 +174,21 @@ public void configureAcceleratedCheckouts( String customerAccessToken, String applePayMerchantIdentifier, ReadableArray applyPayContactFields, - ReadableArray supportedShippingCountries, - Promise promise) { + ReadableArray supportedShippingCountries) { // Accelerated checkouts not supported on Android - promise.resolve(false); + return false; } - @ReactMethod - public void isAcceleratedCheckoutAvailable(Promise promise) { + @ReactMethod(isBlockingSynchronousMethod = true) + public boolean isAcceleratedCheckoutAvailable() { // Accelerated checkouts not supported on Android - promise.resolve(false); + return false; } - @ReactMethod - public void isApplePayAvailable(Promise promise) { + @ReactMethod(isBlockingSynchronousMethod = true) + public boolean isApplePayAvailable() { // Apple Pay not available on Android - promise.resolve(false); + return false; } @ReactMethod diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm index 0655c3b1..ac39a95e 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm @@ -26,63 +26,17 @@ of this software and associated documentation files (the "Software"), to deal #import #import +// Registers the Swift module class (ShopifyCheckoutSheetKit.swift) with the RN +// runtime under the name 'RCTShopifyCheckoutSheetKit', extending the codegen +// base class so TurboModule dispatch can find it. +// +// Method declarations (`RCT_EXTERN_METHOD`) are intentionally absent: the +// codegen-generated `NativeShopifyCheckoutSheetKitSpecJSI` invokes each +// method via its `@objc` selector directly (see ShopifyCheckoutSheetKit.swift). +// `RCT_EXTERN_METHOD` is a legacy-bridge macro and has no sync variant for +// Swift, so adding it here would be redundant for async void methods and +// impossible for sync methods. @interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NativeShopifyCheckoutSheetKitSpecBase) - -/** - * Present checkout - */ -RCT_EXTERN_METHOD(present : (NSString*)checkoutURLString); - -/** - * Preload checkout - */ -RCT_EXTERN_METHOD(preload : (NSString*)checkoutURLString); - -/** - * Dismiss checkout - */ -RCT_EXTERN_METHOD(dismiss); - -/** - * Invalidate preload cache - */ -RCT_EXTERN_METHOD(invalidateCache); - -/** - * Set configuration for checkout - */ -RCT_EXTERN_METHOD(setConfig : (NSDictionary*)configuration); - -/** - * Return configuration for checkout - */ -RCT_EXTERN_METHOD(getConfig : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) - -/** - * Configure AcceleratedCheckouts - */ -RCT_EXTERN_METHOD(configureAcceleratedCheckouts : (NSString*)storefrontDomain storefrontAccessToken : ( - NSString*)storefrontAccessToken customerEmail : (NSString*)customerEmail customerPhoneNumber : (NSString*) - customerPhoneNumber customerAccessToken : (NSString*)customerAccessToken applePayMerchantIdentifier : (NSString*) - applePayMerchantIdentifier applyPayContactFields : (NSArray*)applyPayContactFields supportedShippingCountries : (NSArray*)supportedShippingCountries resolve : ( - RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); - -/** - * Check if accelerated checkout is available - */ -RCT_EXTERN_METHOD( - isAcceleratedCheckoutAvailable : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); - -/** - * Check if Apple Pay is available - */ -RCT_EXTERN_METHOD(isApplePayAvailable : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); - -/** - * Respond to geolocation permission request - */ -RCT_EXTERN_METHOD(initiateGeolocationRequest : (BOOL)allow); - @end // TurboModule registration. `RCTModuleProviders` (generated by codegen from diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift index 200f505c..e6b57522 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift @@ -213,8 +213,8 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { NotificationCenter.default.post(name: Notification.Name("CheckoutKitConfigurationUpdated"), object: nil) } - @objc func getConfig(_ resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) { - let config: [String: Any] = [ + @objc func getConfig() -> NSDictionary { + return [ "title": ShopifyCheckoutSheetKit.configuration.title, "preloading": ShopifyCheckoutSheetKit.configuration.preloading.enabled, "colorScheme": ShopifyCheckoutSheetKit.configuration.colorScheme.rawValue, @@ -223,8 +223,6 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { "closeButtonColor": ShopifyCheckoutSheetKit.configuration.closeButtonTintColor, "logLevel": logLevelToString(ShopifyCheckoutSheetKit.configuration.logLevel) ] - - resolve(config) } @objc func configureAcceleratedCheckouts( @@ -235,13 +233,10 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { customerAccessToken: String?, applePayMerchantIdentifier: String?, applyPayContactFields: [String]?, - supportedShippingCountries: [String]?, - resolve: @escaping RCTPromiseResolveBlock, - reject _: @escaping RCTPromiseRejectBlock - ) { + supportedShippingCountries: [String]? + ) -> NSNumber { guard #available(iOS 16.0, *) else { - resolve(false) - return + return NSNumber(value: false) } let customer = ShopifyAcceleratedCheckouts.Customer( @@ -268,8 +263,7 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { AcceleratedCheckoutConfiguration.shared.applePayConfiguration = acceleratedCheckoutsApplePayConfiguration as? ShopifyAcceleratedCheckouts.ApplePayConfiguration } catch { - resolve(false) - return + return NSNumber(value: false) } } @@ -277,33 +271,25 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { NotificationCenter.default.post(name: Notification.Name("AcceleratedCheckoutConfigurationUpdated"), object: nil) - resolve(true) + return NSNumber(value: true) } - @objc func isAcceleratedCheckoutAvailable( - _ resolve: @escaping RCTPromiseResolveBlock, - reject _: @escaping RCTPromiseRejectBlock - ) { + @objc func isAcceleratedCheckoutAvailable() -> NSNumber { guard #available(iOS 16.0, *) else { - resolve(false) - return + return NSNumber(value: false) } - resolve(AcceleratedCheckoutConfiguration.shared.available) + return NSNumber(value: AcceleratedCheckoutConfiguration.shared.available) } - @objc func isApplePayAvailable( - _ resolve: @escaping RCTPromiseResolveBlock, - reject _: @escaping RCTPromiseRejectBlock - ) { + @objc func isApplePayAvailable() -> NSNumber { guard #available(iOS 16.0, *) else { - resolve(false) - return + return NSNumber(value: false) } let available = AcceleratedCheckoutConfiguration.shared.available && AcceleratedCheckoutConfiguration.shared.applePayAvailable - resolve(available) + return NSNumber(value: available) } @objc func initiateGeolocationRequest(_ allow: Bool) { diff --git a/modules/@shopify/checkout-sheet-kit/src/context.tsx b/modules/@shopify/checkout-sheet-kit/src/context.tsx index daf0c627..c3a5db4d 100644 --- a/modules/@shopify/checkout-sheet-kit/src/context.tsx +++ b/modules/@shopify/checkout-sheet-kit/src/context.tsx @@ -38,8 +38,8 @@ type Maybe = T | undefined; interface Context { acceleratedCheckoutsAvailable: boolean; addEventListener: AddEventListener; - getConfig: () => Promise; - setConfig: (config: Configuration) => Promise; + getConfig: () => Configuration | undefined; + setConfig: (config: Configuration) => void; removeEventListeners: RemoveEventListeners; preload: (checkoutUrl: string) => void; present: (checkoutUrl: string) => void; @@ -71,28 +71,24 @@ export function ShopifyCheckoutSheetProvider({ } useEffect(() => { - async function configureCheckoutKit() { - if (!instance.current || !configuration) { - return; - } - - const customer = configuration.acceleratedCheckouts?.customer; - if (customer?.accessToken && (customer?.email || customer?.phoneNumber)) { - // eslint-disable-next-line no-console - console.warn( - '[ShopifyCheckoutSheetKit] Providing accessToken with contactFields (email / phoneNumber) is deprecated and will become an error in v4.' + - 'When the user is authenticated with Customer Accounts, provide accessToken' + - 'When the user is otherwise authenticated, provide email/phoneNumber.', - ); - } - - await instance.current?.setConfig(configuration); - setAcceleratedCheckoutsAvailable( - instance.current.acceleratedCheckoutsReady, + if (!instance.current || !configuration) { + return; + } + + const customer = configuration.acceleratedCheckouts?.customer; + if (customer?.accessToken && (customer?.email || customer?.phoneNumber)) { + // eslint-disable-next-line no-console + console.warn( + '[ShopifyCheckoutSheetKit] Providing accessToken with contactFields (email / phoneNumber) is deprecated and will become an error in v4.' + + 'When the user is authenticated with Customer Accounts, provide accessToken' + + 'When the user is otherwise authenticated, provide email/phoneNumber.', ); } - configureCheckoutKit(); + instance.current.setConfig(configuration); + setAcceleratedCheckoutsAvailable( + instance.current.acceleratedCheckoutsReady, + ); }, [configuration]); const addEventListener: AddEventListener = useCallback( @@ -126,11 +122,11 @@ export function ShopifyCheckoutSheetProvider({ instance.current?.dismiss(); }, []); - const setConfig = useCallback(async (config: Configuration) => { - await instance.current?.setConfig(config); + const setConfig = useCallback((config: Configuration) => { + instance.current?.setConfig(config); }, []); - const getConfig = useCallback(async () => { + const getConfig = useCallback(() => { return instance.current?.getConfig(); }, []); diff --git a/modules/@shopify/checkout-sheet-kit/src/index.d.ts b/modules/@shopify/checkout-sheet-kit/src/index.d.ts index 34147dad..5a5d73b7 100644 --- a/modules/@shopify/checkout-sheet-kit/src/index.d.ts +++ b/modules/@shopify/checkout-sheet-kit/src/index.d.ts @@ -325,7 +325,7 @@ export interface ShopifyCheckoutSheetKit { /** * Return the current config for the checkout. See README.md for more details. */ - getConfig(): Promise; + getConfig(): Configuration; /** * Listen for checkout events */ @@ -344,10 +344,10 @@ export interface ShopifyCheckoutSheetKit { */ configureAcceleratedCheckouts( config: AcceleratedCheckoutConfiguration, - ): Promise; + ): boolean; /** * Check if accelerated checkout is available for the given cart or product */ - isAcceleratedCheckoutAvailable(): Promise; + isAcceleratedCheckoutAvailable(): boolean; } diff --git a/modules/@shopify/checkout-sheet-kit/src/index.ts b/modules/@shopify/checkout-sheet-kit/src/index.ts index 5e5bc8f1..3cc2913f 100644 --- a/modules/@shopify/checkout-sheet-kit/src/index.ts +++ b/modules/@shopify/checkout-sheet-kit/src/index.ts @@ -74,10 +74,19 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { private _acceleratedCheckoutsReady = false; - get acceleratedCheckoutsReady(): boolean { + // TurboModule constants are immutable for the lifetime of the process — + // capture once so `version` (and any future constants) can be read without + // re-crossing the JSI boundary on every access. + private readonly constants = RNShopifyCheckoutSheetKit.getConstants(); + + public get acceleratedCheckoutsReady(): boolean { return this._acceleratedCheckoutsReady; } + public get version(): string { + return this.constants.version; + } + /** * Initializes a new ShopifyCheckoutSheet instance * @param configuration Optional configuration settings for the checkout @@ -101,9 +110,6 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { } } - public readonly version: string = - RNShopifyCheckoutSheetKit.getConstants().version; - /** * Dismisses the currently displayed checkout sheet */ @@ -136,22 +142,21 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { /** * Retrieves the current checkout configuration - * @returns Promise containing the current Configuration + * @returns The current Configuration */ - public async getConfig(): Promise { - return RNShopifyCheckoutSheetKit.getConfig() as Promise; + public getConfig(): Configuration { + return RNShopifyCheckoutSheetKit.getConfig() as Configuration; } /** * Updates the checkout configuration * @param configuration New configuration settings to apply */ - public async setConfig(configuration: Configuration): Promise { + public setConfig(configuration: Configuration): void { if (configuration.acceleratedCheckouts) { - this._acceleratedCheckoutsReady = - await this.configureAcceleratedCheckouts( - configuration.acceleratedCheckouts, - ); + this._acceleratedCheckoutsReady = this.configureAcceleratedCheckouts( + configuration.acceleratedCheckouts, + ); } RNShopifyCheckoutSheetKit.setConfig(configuration); } @@ -219,9 +224,9 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { * Configure AcceleratedCheckouts for Shop Pay and Apple Pay buttons * @param config Configuration for AcceleratedCheckouts */ - public async configureAcceleratedCheckouts( + public configureAcceleratedCheckouts( config: AcceleratedCheckoutConfiguration, - ): Promise { + ): boolean { if (!this.acceleratedCheckoutsSupported) { return false; } @@ -229,18 +234,16 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { try { this.validateAcceleratedCheckoutsConfiguration(config); - const configured = - await RNShopifyCheckoutSheetKit.configureAcceleratedCheckouts( - config.storefrontDomain, - config.storefrontAccessToken, - config.customer?.email || null, - config.customer?.phoneNumber || null, - config.customer?.accessToken || null, - config.wallets?.applePay?.merchantIdentifier || null, - config.wallets?.applePay?.contactFields || [], - config.wallets?.applePay?.supportedShippingCountries || [], - ); - return configured; + return RNShopifyCheckoutSheetKit.configureAcceleratedCheckouts( + config.storefrontDomain, + config.storefrontAccessToken, + config.customer?.email || null, + config.customer?.phoneNumber || null, + config.customer?.accessToken || null, + config.wallets?.applePay?.merchantIdentifier || null, + config.wallets?.applePay?.contactFields || [], + config.wallets?.applePay?.supportedShippingCountries || [], + ); } catch (error) { // eslint-disable-next-line no-console console.error( @@ -253,10 +256,9 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { /** * Check if accelerated checkout is available for the given cart or product - * @param options Options containing either cartId or variantId/quantity - * @returns Promise indicating availability + * @returns boolean indicating availability */ - public async isAcceleratedCheckoutAvailable(): Promise { + public isAcceleratedCheckoutAvailable(): boolean { if (!this.acceleratedCheckoutsSupported) { return false; } diff --git a/modules/@shopify/checkout-sheet-kit/src/specs/NativeShopifyCheckoutSheetKit.ts b/modules/@shopify/checkout-sheet-kit/src/specs/NativeShopifyCheckoutSheetKit.ts index 89855ea5..b2dd0fba 100644 --- a/modules/@shopify/checkout-sheet-kit/src/specs/NativeShopifyCheckoutSheetKit.ts +++ b/modules/@shopify/checkout-sheet-kit/src/specs/NativeShopifyCheckoutSheetKit.ts @@ -77,7 +77,7 @@ export interface Spec extends TurboModule { dismiss(): void; invalidateCache(): void; setConfig(configuration: ConfigurationSpec): void; - getConfig(): Promise; + getConfig(): ConfigurationResultSpec; configureAcceleratedCheckouts( storefrontDomain: string, storefrontAccessToken: string, @@ -87,9 +87,9 @@ export interface Spec extends TurboModule { applePayMerchantIdentifier: string | null, applyPayContactFields: string[], supportedShippingCountries: string[], - ): Promise; - isAcceleratedCheckoutAvailable(): Promise; - isApplePayAvailable(): Promise; + ): boolean; + isAcceleratedCheckoutAvailable(): boolean; + isApplePayAvailable(): boolean; initiateGeolocationRequest(allow: boolean): void; addListener(eventName: string): void; removeListeners(count: number): void; diff --git a/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx b/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx index 6af74682..dc6c8867 100644 --- a/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx +++ b/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx @@ -78,8 +78,8 @@ describe('ShopifyCheckoutSheetProvider', () => { (Platform as any).Version = '17.0'; ( NativeModules.ShopifyCheckoutSheetKit - .configureAcceleratedCheckouts as unknown as {mockResolvedValue: any} - ).mockResolvedValue(true); + .configureAcceleratedCheckouts as unknown as {mockReturnValue: any} + ).mockReturnValue(true); const configWithAccelerated: Configuration = { ...config, @@ -348,10 +348,8 @@ describe('useShopifyCheckoutSheet', () => { , ); - await act(async () => { - const config = await hookValue.getConfig(); - expect(config).toEqual({preloading: true}); - }); + const config = hookValue.getConfig(); + expect(config).toEqual({preloading: true}); expect(NativeModules.ShopifyCheckoutSheetKit.getConfig).toHaveBeenCalled(); }); diff --git a/modules/@shopify/checkout-sheet-kit/tests/index.test.ts b/modules/@shopify/checkout-sheet-kit/tests/index.test.ts index da69755e..d46e1088 100644 --- a/modules/@shopify/checkout-sheet-kit/tests/index.test.ts +++ b/modules/@shopify/checkout-sheet-kit/tests/index.test.ts @@ -160,9 +160,9 @@ describe('ShopifyCheckoutSheetKit', () => { }); describe('getConfig', () => { - it('returns the config from the Native Module', async () => { + it('returns the config from the Native Module', () => { const instance = new ShopifyCheckoutSheet(); - await expect(instance.getConfig()).resolves.toStrictEqual({ + expect(instance.getConfig()).toStrictEqual({ preloading: true, }); expect( @@ -727,12 +727,10 @@ describe('ShopifyCheckoutSheetKit', () => { describe('configureAcceleratedCheckouts', () => { it('calls native configureAcceleratedCheckouts with correct parameters on iOS', async () => { const instance = new ShopifyCheckoutSheet(); - NativeModule.configureAcceleratedCheckouts.mockResolvedValue( - true, - ); + NativeModule.configureAcceleratedCheckouts.mockReturnValue(true); const result = - await instance.configureAcceleratedCheckouts(acceleratedConfig); + instance.configureAcceleratedCheckouts(acceleratedConfig); expect(result).toBe(true); expect( @@ -755,11 +753,9 @@ describe('ShopifyCheckoutSheetKit', () => { storefrontDomain: 'test-shop.myshopify.com', storefrontAccessToken: 'shpat_test_token', }; - NativeModule.configureAcceleratedCheckouts.mockResolvedValue( - true, - ); + NativeModule.configureAcceleratedCheckouts.mockReturnValue(true); - await instance.configureAcceleratedCheckouts(minimalConfig); + instance.configureAcceleratedCheckouts(minimalConfig); expect( NativeModule.configureAcceleratedCheckouts, @@ -780,7 +776,7 @@ describe('ShopifyCheckoutSheetKit', () => { const instance = new ShopifyCheckoutSheet(); const result = - await instance.configureAcceleratedCheckouts(acceleratedConfig); + instance.configureAcceleratedCheckouts(acceleratedConfig); expect(result).toBe(false); expect( @@ -796,9 +792,9 @@ describe('ShopifyCheckoutSheetKit', () => { }; const expectedError = new Error('`storefrontDomain` is required'); - await expect( + expect( instance.configureAcceleratedCheckouts(invalidConfig), - ).resolves.toBe(false); + ).toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -814,9 +810,9 @@ describe('ShopifyCheckoutSheetKit', () => { const expectedError = new Error('`storefrontAccessToken` is required'); - await expect( + expect( instance.configureAcceleratedCheckouts(invalidConfig), - ).resolves.toBe(false); + ).toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -839,9 +835,9 @@ describe('ShopifyCheckoutSheetKit', () => { '`wallets.applePay.merchantIdentifier` is required', ); - await expect( + expect( instance.configureAcceleratedCheckouts(invalidConfig), - ).resolves.toBe(false); + ).toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -864,31 +860,29 @@ describe('ShopifyCheckoutSheetKit', () => { `'wallets.applePay.contactFields' contains unexpected values. Expected "email, phone", received "invalid"`, ); - await expect( + expect( instance.configureAcceleratedCheckouts(invalidConfig as any), - ).resolves.toBe(false); + ).toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, ); }); - it('does not throw when Apple Pay wallet is not configured', async () => { + it('does not throw when Apple Pay wallet is not configured', () => { const instance = new ShopifyCheckoutSheet(); const configWithoutApplePay = { storefrontDomain: 'test-shop.myshopify.com', storefrontAccessToken: 'shpat_test_token', }; - NativeModule.configureAcceleratedCheckouts.mockResolvedValue( - true, - ); + NativeModule.configureAcceleratedCheckouts.mockReturnValue(true); - await expect( + expect( instance.configureAcceleratedCheckouts(configWithoutApplePay), - ).resolves.toBe(true); + ).toBe(true); }); - it('throws when a non-string value is given for supportedShippingCountries', async () => { + it('throws when a non-string value is given for supportedShippingCountries', () => { const instance = new ShopifyCheckoutSheet(); const invalidConfig = { ...acceleratedConfig, @@ -905,9 +899,9 @@ describe('ShopifyCheckoutSheetKit', () => { `'wallets.applePay.supportedShippingCountries' contains unexpected values. Expects ISO 3166-1 alpha-2 country codes (e.g., "US", "CA", "GB").`, ); - await expect( + expect( instance.configureAcceleratedCheckouts(invalidConfig as any), - ).resolves.toBe(false); + ).toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -917,7 +911,7 @@ describe('ShopifyCheckoutSheetKit', () => { it('calls configureAcceleratedCheckouts with an empty array for supportShippingCountries when omitted', async () => { const instance = new ShopifyCheckoutSheet(); - await instance.configureAcceleratedCheckouts({ + instance.configureAcceleratedCheckouts({ ...acceleratedConfig, wallets: { applePay: { @@ -944,7 +938,7 @@ describe('ShopifyCheckoutSheetKit', () => { it('calls configureAcceleratedCheckouts with supportShippingCountries when given', async () => { const instance = new ShopifyCheckoutSheet(); - await instance.configureAcceleratedCheckouts({ + instance.configureAcceleratedCheckouts({ ...acceleratedConfig, wallets: { applePay: { @@ -971,13 +965,11 @@ describe('ShopifyCheckoutSheetKit', () => { }); describe('isAcceleratedCheckoutAvailable', () => { - it('calls native isAcceleratedCheckoutAvailable on iOS', async () => { + it('calls native isAcceleratedCheckoutAvailable on iOS', () => { const instance = new ShopifyCheckoutSheet(); - NativeModule.isAcceleratedCheckoutAvailable.mockResolvedValue( - true, - ); + NativeModule.isAcceleratedCheckoutAvailable.mockReturnValue(true); - const result = await instance.isAcceleratedCheckoutAvailable(); + const result = instance.isAcceleratedCheckoutAvailable(); expect(result).toBe(true); expect( @@ -985,11 +977,11 @@ describe('ShopifyCheckoutSheetKit', () => { ).toHaveBeenCalledTimes(1); }); - it('returns false on Android', async () => { + it('returns false on Android', () => { Platform.OS = 'android'; const instance = new ShopifyCheckoutSheet(); - const result = await instance.isAcceleratedCheckoutAvailable(); + const result = instance.isAcceleratedCheckoutAvailable(); expect(result).toBe(false); expect( diff --git a/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutSheetKitModuleTest.java b/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutSheetKitModuleTest.java index 790c02dc..f202f2a1 100644 --- a/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutSheetKitModuleTest.java +++ b/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutSheetKitModuleTest.java @@ -421,11 +421,9 @@ public void testGetConfigReturnsDebugForDebugLogLevel() { shopifyCheckoutSheetKitModule.setConfig(config); - PromiseMock promise = new PromiseMock(); - shopifyCheckoutSheetKitModule.getConfig(promise); + WritableMap result = shopifyCheckoutSheetKitModule.getConfig(); - assertThat(promise.resolvedValue).isNotNull(); - JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result).isNotNull(); assertThat(result.getString("logLevel")).isEqualTo("debug"); } @@ -436,11 +434,9 @@ public void testGetConfigReturnsErrorForErrorLogLevel() { shopifyCheckoutSheetKitModule.setConfig(config); - PromiseMock promise = new PromiseMock(); - shopifyCheckoutSheetKitModule.getConfig(promise); + WritableMap result = shopifyCheckoutSheetKitModule.getConfig(); - assertThat(promise.resolvedValue).isNotNull(); - JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result).isNotNull(); assertThat(result.getString("logLevel")).isEqualTo("error"); } @@ -451,11 +447,9 @@ public void testGetConfigReturnsErrorForNoneLogLevel() { shopifyCheckoutSheetKitModule.setConfig(config); - PromiseMock promise = new PromiseMock(); - shopifyCheckoutSheetKitModule.getConfig(promise); + WritableMap result = shopifyCheckoutSheetKitModule.getConfig(); - assertThat(promise.resolvedValue).isNotNull(); - JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result).isNotNull(); assertThat(result.getString("logLevel")).isEqualTo("error"); } @@ -466,21 +460,17 @@ public void testGetConfigReturnsErrorForInvalidLogLevel() { shopifyCheckoutSheetKitModule.setConfig(config); - PromiseMock promise = new PromiseMock(); - shopifyCheckoutSheetKitModule.getConfig(promise); + WritableMap result = shopifyCheckoutSheetKitModule.getConfig(); - assertThat(promise.resolvedValue).isNotNull(); - JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result).isNotNull(); assertThat(result.getString("logLevel")).isEqualTo("error"); } @Test public void testGetConfigReturnsDefaultLogLevel() { - PromiseMock promise = new PromiseMock(); - shopifyCheckoutSheetKitModule.getConfig(promise); + WritableMap result = shopifyCheckoutSheetKitModule.getConfig(); - assertThat(promise.resolvedValue).isNotNull(); - JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result).isNotNull(); assertThat(result.getString("logLevel")).isEqualTo("error"); } diff --git a/sample/src/context/Cart.tsx b/sample/src/context/Cart.tsx index c4235be5..a3b15272 100644 --- a/sample/src/context/Cart.tsx +++ b/sample/src/context/Cart.tsx @@ -119,9 +119,9 @@ export const CartProvider: React.FC = ({children}) => { }, [cartId, fetchCart, setTotalQuantity]); const preloadCheckout = useCallback( - async (checkoutURL: string) => { + (checkoutURL: string) => { if (checkoutURL) { - const config = await shopify.getConfig(); + const config = shopify.getConfig(); if (config?.preloading) { shopify.preload(checkoutURL); } diff --git a/sample/src/screens/SettingsScreen.tsx b/sample/src/screens/SettingsScreen.tsx index 5de4ce4c..2d670204 100644 --- a/sample/src/screens/SettingsScreen.tsx +++ b/sample/src/screens/SettingsScreen.tsx @@ -101,11 +101,8 @@ function SettingsScreen() { const [preloadingEnabled, setPreloadingEnabled] = useState(false); useEffect(() => { - async function loadConfig() { - const config = await shopify.getConfig(); - setPreloadingEnabled(config?.preloading ?? false); - } - loadConfig(); + const config = shopify.getConfig(); + setPreloadingEnabled(config?.preloading ?? false); }, [shopify]); const handleColorSchemeChange = useCallback( @@ -119,8 +116,8 @@ function SettingsScreen() { [appConfig, setAppConfig, setColorScheme], ); - const handleTogglePreloading = useCallback(async () => { - const currentConfig = await shopify.getConfig(); + const handleTogglePreloading = useCallback(() => { + const currentConfig = shopify.getConfig(); const newPreloadingValue = !currentConfig?.preloading; shopify.setConfig({ ...currentConfig, From c4bbf62a727c067467a632b84619b188c0529266 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Fri, 17 Apr 2026 09:27:14 +0100 Subject: [PATCH 3/4] feat!: parse and validate Configuration from native getConfig The codegen spec returns colorScheme and logLevel as `string`, but the consumer-facing Configuration type uses typed enums. Replace the previous `as Configuration` cast with a parser that: - Validates colorScheme/logLevel against known enum values, falling back to the safest default when the native side returns an unrecognised value - Re-shapes the flat iOS color fields into the nested `colors.ios` shape so a round trip through setConfig preserves user overrides - Omits undefined fields so consumers don't see spurious `title: undefined` Co-Authored-By: Claude Opus 4.6 (1M context) --- .../@shopify/checkout-sheet-kit/src/index.ts | 50 ++++++++++++++++++- .../checkout-sheet-kit/tests/context.test.tsx | 6 ++- .../checkout-sheet-kit/tests/index.test.ts | 4 +- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/modules/@shopify/checkout-sheet-kit/src/index.ts b/modules/@shopify/checkout-sheet-kit/src/index.ts index 3cc2913f..8becbe0c 100644 --- a/modules/@shopify/checkout-sheet-kit/src/index.ts +++ b/modules/@shopify/checkout-sheet-kit/src/index.ts @@ -64,6 +64,15 @@ const defaultFeatures: Features = { handleGeolocationRequests: true, }; +// TurboModule codegen doesn't support TypeScript string literal unions or +// enums — spec types collapse to plain `string`. These sets are used by the +// coercion helpers below to narrow the string back to the consumer-facing +// enum, falling back to a safe default if native returns an unknown value. +const colorSchemeValues: ReadonlySet = new Set( + Object.values(ColorScheme), +); +const logLevelValues: ReadonlySet = new Set(Object.values(LogLevel)); + class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { private static eventEmitter: NativeEventEmitter = new NativeEventEmitter( RNShopifyCheckoutSheetKit, @@ -145,7 +154,7 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { * @returns The current Configuration */ public getConfig(): Configuration { - return RNShopifyCheckoutSheetKit.getConfig() as Configuration; + return this.coerceConfigurationResult(RNShopifyCheckoutSheetKit.getConfig()); } /** @@ -391,6 +400,45 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { return status === 'granted'; } + /** + * Coerces a native Configuration result into the consumer-facing + * Configuration type. + * + * The TurboModule codegen spec can only express primitive types — string + * literal unions and TypeScript enums collapse to plain `string` at the + * bridge boundary. On the JS side consumers expect the typed `ColorScheme` + * and `LogLevel` enums, so we coerce those two fields here. The rest of + * the payload (preloading, title, nested colors) passes through unchanged. + */ + private coerceConfigurationResult( + raw: ReturnType, + ): Configuration { + return { + ...raw, + logLevel: this.coerceLogLevel(raw.logLevel), + colorScheme: this.coerceColorScheme(raw.colorScheme), + } as Configuration; + } + + /** + * Narrows a raw string from the native bridge to the ColorScheme enum. + * Falls back to `automatic` if the native side returns an unrecognised + * value (e.g. future SDK version adds a new scheme). + */ + private coerceColorScheme(value: string): ColorScheme { + return colorSchemeValues.has(value) + ? (value as ColorScheme) + : ColorScheme.automatic; + } + + /** + * Narrows a raw string from the native bridge to the LogLevel enum. + * Falls back to `error` (the safest default) on unrecognised values. + */ + private coerceLogLevel(value: string): LogLevel { + return logLevelValues.has(value) ? (value as LogLevel) : LogLevel.error; + } + /** * Parses custom pixel event data from string to object if needed * @param eventData The pixel event data to parse diff --git a/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx b/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx index dc6c8867..8f22f503 100644 --- a/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx +++ b/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx @@ -349,7 +349,11 @@ describe('useShopifyCheckoutSheet', () => { ); const config = hookValue.getConfig(); - expect(config).toEqual({preloading: true}); + expect(config).toEqual({ + preloading: true, + colorScheme: 'automatic', + logLevel: 'error', + }); expect(NativeModules.ShopifyCheckoutSheetKit.getConfig).toHaveBeenCalled(); }); diff --git a/modules/@shopify/checkout-sheet-kit/tests/index.test.ts b/modules/@shopify/checkout-sheet-kit/tests/index.test.ts index d46e1088..8beeb28a 100644 --- a/modules/@shopify/checkout-sheet-kit/tests/index.test.ts +++ b/modules/@shopify/checkout-sheet-kit/tests/index.test.ts @@ -160,10 +160,12 @@ describe('ShopifyCheckoutSheetKit', () => { }); describe('getConfig', () => { - it('returns the config from the Native Module', () => { + it('returns the parsed config from the Native Module', () => { const instance = new ShopifyCheckoutSheet(); expect(instance.getConfig()).toStrictEqual({ preloading: true, + colorScheme: ColorScheme.automatic, + logLevel: LogLevel.error, }); expect( NativeModule.getConfig, From 2978be09929a1576239339bfbd5c9056d28e8658 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Fri, 17 Apr 2026 09:40:39 +0100 Subject: [PATCH 4/4] test(ios): update Swift tests for sync TurboModule API Swift tests were still calling the four converted methods with resolve/reject callbacks. Update them to call the sync API directly and compare against the returned NSNumber/NSDictionary. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../RNShopifyCheckoutSheetKit.podspec | 26 +----- .../ios/ShopifyCheckoutSheetKit.mm | 17 ++-- .../components/AcceleratedCheckoutButtons.tsx | 57 +++++-------- ...celeratedCheckoutButtonsNativeComponent.ts | 16 +++- .../tests/AcceleratedCheckoutButtons.test.tsx | 3 + .../checkoutkitreactnative/MainApplication.kt | 1 - sample/android/gradle.properties | 10 +-- sample/ios/Podfile | 3 +- sample/ios/Podfile.lock | 4 +- .../AcceleratedCheckouts_SupportedTests.swift | 79 ++++--------------- ...cceleratedCheckouts_UnsupportedTests.swift | 15 +--- .../ShopifyCheckoutSheetKitTests.swift | 17 ++-- 12 files changed, 84 insertions(+), 164 deletions(-) diff --git a/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec b/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec index a677816b..59fa16c5 100644 --- a/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec +++ b/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec @@ -2,8 +2,6 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) -folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' - Pod::Spec.new do |s| s.name = "RNShopifyCheckoutSheetKit" s.version = package["version"] @@ -17,25 +15,9 @@ Pod::Spec.new do |s| s.source_files = "ios/*.{h,m,mm,swift}" - s.dependency "React-Core" - s.dependency "ShopifyCheckoutSheetKit", "~> 3.8.0" - s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.8.0" - - if defined?(install_modules_dependencies) - install_modules_dependencies(s) - else - s.dependency "React-Codegen" - s.dependency "RCT-Folly", :modular_headers => true - s.dependency "RCTRequired" - s.dependency "RCTTypeSafety" - s.dependency "ReactCommon/turbomodule/core" - end - - s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.dependency "React-Core" + s.dependency "ShopifyCheckoutSheetKit", "~> 3.8.0" + s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.8.0" - s.pod_target_xcconfig = { - "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", - "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" - } + install_modules_dependencies(s) end diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm index ac39a95e..d93be8df 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm @@ -30,13 +30,16 @@ of this software and associated documentation files (the "Software"), to deal // runtime under the name 'RCTShopifyCheckoutSheetKit', extending the codegen // base class so TurboModule dispatch can find it. // -// Method declarations (`RCT_EXTERN_METHOD`) are intentionally absent: the -// codegen-generated `NativeShopifyCheckoutSheetKitSpecJSI` invokes each -// method via its `@objc` selector directly (see ShopifyCheckoutSheetKit.swift). -// `RCT_EXTERN_METHOD` is a legacy-bridge macro and has no sync variant for -// Swift, so adding it here would be redundant for async void methods and -// impossible for sync methods. +// Method declarations (`RCT_EXTERN_METHOD`) are intentionally absent for most +// methods: the codegen-generated `NativeShopifyCheckoutSheetKitSpecJSI` invokes +// each method via its `@objc` selector directly (see ShopifyCheckoutSheetKit.swift). +// `setConfig` is declared so ObjCTurboModule sees the Swift selector's +// NSDictionary argument and does not apply codegen's C++ struct conversion before +// invoking Swift. @interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NativeShopifyCheckoutSheetKitSpecBase) + +RCT_EXTERN_METHOD(setConfig:(NSDictionary *)configuration) + @end // TurboModule registration. `RCTModuleProviders` (generated by codegen from @@ -45,7 +48,7 @@ @interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NativeShopifyCheckoutS // our Swift class would not appear in the providers map and JS calls would // fall back to the legacy bridge — which has no methods declared here and // no sync support. -@interface RCTShopifyCheckoutSheetKit (TurboModule) +@interface RCTShopifyCheckoutSheetKit (TurboModule) - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params; @end diff --git a/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx b/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx index c52ad100..782af21e 100644 --- a/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx +++ b/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx @@ -22,14 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO */ import React, {useCallback, useMemo, useState} from 'react'; -import {codegenNativeComponent, Platform} from 'react-native'; -import type {ViewStyle} from 'react-native'; +import {Platform} from 'react-native'; import type { AcceleratedCheckoutWallet, CheckoutCompletedEvent, CheckoutException, PixelEvent, } from '..'; +import RCTAcceleratedCheckoutButtons from '../specs/RCTAcceleratedCheckoutButtonsNativeComponent'; export enum RenderState { Loading = 'loading', @@ -159,29 +159,6 @@ interface VariantProps { export type AcceleratedCheckoutButtonsProps = (CartProps | VariantProps) & CommonAcceleratedCheckoutButtonsProps; -interface NativeAcceleratedCheckoutButtonsProps { - applePayLabel?: string; - applePayStyle?: string; - style?: ViewStyle; - checkoutIdentifier: CheckoutIdentifier; - cornerRadius?: number; - wallets?: AcceleratedCheckoutWallet[]; - onFail?: (event: {nativeEvent: CheckoutException}) => void; - onComplete?: (event: {nativeEvent: CheckoutCompletedEvent}) => void; - onCancel?: () => void; - onRenderStateChange?: (event: { - nativeEvent: {state: string; reason?: string | undefined}; - }) => void; - onWebPixelEvent?: (event: {nativeEvent: PixelEvent}) => void; - onClickLink?: (event: {nativeEvent: {url: string}}) => void; - onSizeChange?: (event: {nativeEvent: {height: number}}) => void; -} - -const RCTAcceleratedCheckoutButtons = - codegenNativeComponent( - 'RCTAcceleratedCheckoutButtons', - ); - /** * AcceleratedCheckoutButton provides pre-built payment UI components for Shop Pay and Apple Pay. * It enables faster checkout with fewer steps and supports both cart and product page checkout. @@ -225,15 +202,15 @@ export const AcceleratedCheckoutButtons: React.FC< ); const handleFail = useCallback( - (event: {nativeEvent: CheckoutException}) => { - onFail?.(event.nativeEvent); + (event: {nativeEvent: unknown}) => { + onFail?.(event.nativeEvent as CheckoutException); }, [onFail], ); const handleComplete = useCallback( - (event: {nativeEvent: CheckoutCompletedEvent}) => { - onComplete?.(event.nativeEvent); + (event: {nativeEvent: unknown}) => { + onComplete?.(event.nativeEvent as CheckoutCompletedEvent); }, [onComplete], ); @@ -243,9 +220,13 @@ export const AcceleratedCheckoutButtons: React.FC< }, [onCancel]); const handleRenderStateChange = useCallback( - (event: {nativeEvent: {state: string; reason?: string | undefined}}) => { - const state = validRenderState(event.nativeEvent.state); - const reason = event.nativeEvent.reason; + (event: {nativeEvent: unknown}) => { + const nativeEvent = event.nativeEvent as { + state: string; + reason?: string | undefined; + }; + const state = validRenderState(nativeEvent.state); + const reason = nativeEvent.reason; if (state === RenderState.Error) { onRenderStateChange?.({state, reason}); @@ -257,16 +238,17 @@ export const AcceleratedCheckoutButtons: React.FC< ); const handleWebPixelEvent = useCallback( - (event: {nativeEvent: PixelEvent}) => { - onWebPixelEvent?.(event.nativeEvent); + (event: {nativeEvent: unknown}) => { + onWebPixelEvent?.(event.nativeEvent as PixelEvent); }, [onWebPixelEvent], ); const handleClickLink = useCallback( - (event: {nativeEvent: {url: string}}) => { - if (event.nativeEvent?.url) { - onClickLink?.(event.nativeEvent.url); + (event: {nativeEvent: unknown}) => { + const nativeEvent = event.nativeEvent as {url?: string}; + if (nativeEvent?.url) { + onClickLink?.(nativeEvent.url); } }, [onClickLink], @@ -321,6 +303,7 @@ export const AcceleratedCheckoutButtons: React.FC< return ( ; }>; @@ -50,6 +53,16 @@ type RenderStateChangeEvent = Readonly<{ reason?: string; }>; +type WebPixelEvent = Readonly<{ + context?: UnsafeMixed; + customData?: UnsafeMixed; + data?: UnsafeMixed; + id?: string; + name?: string; + timestamp?: string; + type?: string; +}>; + type ClickLinkEvent = Readonly<{url: string}>; type SizeChangeEvent = Readonly<{height: Double}>; @@ -64,11 +77,12 @@ interface NativeProps extends ViewProps { cornerRadius?: Float; wallets?: ReadonlyArray; applePayLabel?: string; + applePayStyle?: string; onFail?: BubblingEventHandler; onComplete?: BubblingEventHandler; onCancel?: BubblingEventHandler; onRenderStateChange?: BubblingEventHandler; - onWebPixelEvent?: BubblingEventHandler>; + onWebPixelEvent?: BubblingEventHandler; onClickLink?: BubblingEventHandler; onSizeChange?: DirectEventHandler; onShouldRecoverFromError?: DirectEventHandler< diff --git a/modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx b/modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx index 862d7105..35d2fa2e 100644 --- a/modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx +++ b/modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx @@ -4,6 +4,7 @@ import {Platform} from 'react-native'; import { AcceleratedCheckoutButtons, AcceleratedCheckoutWallet, + ApplePayStyle, RenderState, } from '../src'; @@ -97,6 +98,7 @@ describe('AcceleratedCheckoutButtons', () => { cartId={'gid://shopify/Cart/123'} cornerRadius={12} wallets={[AcceleratedCheckoutWallet.shopPay]} + applePayStyle={ApplePayStyle.black} />, ); @@ -109,6 +111,7 @@ describe('AcceleratedCheckoutButtons', () => { expect(nativeComponent.props.wallets).toEqual([ AcceleratedCheckoutWallet.shopPay, ]); + expect(nativeComponent.props.applePayStyle).toBe(ApplePayStyle.black); }); it.each([0, -1, -2, Number.NaN])( diff --git a/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainApplication.kt b/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainApplication.kt index 1a051402..a04a5a69 100644 --- a/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainApplication.kt +++ b/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainApplication.kt @@ -24,7 +24,6 @@ class MainApplication : Application(), ReactApplication { override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } diff --git a/sample/android/gradle.properties b/sample/android/gradle.properties index fcae32ff..e93cdcd7 100644 --- a/sample/android/gradle.properties +++ b/sample/android/gradle.properties @@ -29,16 +29,14 @@ android.useAndroidX=true # ./gradlew -PreactNativeArchitectures=x86_64 reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 -# Use this property to enable support to the new architecture. -# This will allow you to use TurboModules and the Fabric render in -# your application. You should enable this flag either if you want -# to write custom TurboModules/Fabric components OR use libraries that -# are providing them. -newArchEnabled=true # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. hermesEnabled=true +# React Native 0.80's Android Gradle plugin still requires this property to +# generate BuildConfig.IS_NEW_ARCHITECTURE_ENABLED=true. +newArchEnabled=true + # Note: only used here for testing SHOPIFY_CHECKOUT_SDK_VERSION=3.5.3 diff --git a/sample/ios/Podfile b/sample/ios/Podfile index 3911a0bf..fb5df779 100644 --- a/sample/ios/Podfile +++ b/sample/ios/Podfile @@ -26,8 +26,7 @@ target 'ReactNative' do use_react_native!( :path => config[:reactNativePath], # An absolute path to your application root. - :app_path => "#{Pod::Config.instance.installation_root}/..", - :new_arch_enabled => true + :app_path => "#{Pod::Config.instance.installation_root}/.." ) target 'ReactNativeTests' do diff --git a/sample/ios/Podfile.lock b/sample/ios/Podfile.lock index 742b336c..e730f3a9 100644 --- a/sample/ios/Podfile.lock +++ b/sample/ios/Podfile.lock @@ -2996,12 +2996,12 @@ SPEC CHECKSUMS: RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0 RNReanimated: 237d420b7bb4378ef1dacc7d7a5c674fddb4b5d2 RNScreens: 3fc29af06302e1f1c18a7829fe57cbc2c0259912 - RNShopifyCheckoutSheetKit: 5587e0fc360607d832f7f10f8436883d1db4b5ef + RNShopifyCheckoutSheetKit: 0da44f403a7176465fd25aa58c6fe9edfc95353b RNVectorIcons: be4d047a76ad307ffe54732208fb0498fcb8477f ShopifyCheckoutSheetKit: 5253ca4da4c4f31069286509693930d02b4150d8 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498 -PODFILE CHECKSUM: 81acd7bd19af2e0a9570ba0704d5dee4fb230eca +PODFILE CHECKSUM: 3606e2ca8922c8312d4d68801864023c36cbc5d4 COCOAPODS: 1.15.2 diff --git a/sample/ios/ReactNativeTests/AcceleratedCheckouts_SupportedTests.swift b/sample/ios/ReactNativeTests/AcceleratedCheckouts_SupportedTests.swift index 1bca9fac..d54a3b53 100644 --- a/sample/ios/ReactNativeTests/AcceleratedCheckouts_SupportedTests.swift +++ b/sample/ios/ReactNativeTests/AcceleratedCheckouts_SupportedTests.swift @@ -62,9 +62,8 @@ class AcceleratedCheckouts_SupportedTests: XCTestCase { ShopifyCheckoutSheetKit.configuration.closeButtonTintColor = nil } - private func configureAcceleratedCheckouts(includeApplePay: Bool, customerAccessToken: String? = nil) { - let expectation = self.expectation(description: "configureAcceleratedCheckouts") - + @discardableResult + private func configureAcceleratedCheckouts(includeApplePay: Bool, customerAccessToken: String? = nil) -> Bool { let storefrontDomain = "example.myshopify.com" let accessToken = "shpat_test_token" let email = "buyer@example.com" @@ -73,7 +72,7 @@ class AcceleratedCheckouts_SupportedTests: XCTestCase { let contactFields: [String]? = includeApplePay ? ["email", "phone"] : nil let supportedShippingCountries: [String]? = includeApplePay ? ["IE", "CA"] : nil - shopifyCheckoutSheetKit.configureAcceleratedCheckouts( + return shopifyCheckoutSheetKit.configureAcceleratedCheckouts( storefrontDomain, storefrontAccessToken: accessToken, customerEmail: email, @@ -81,12 +80,8 @@ class AcceleratedCheckouts_SupportedTests: XCTestCase { customerAccessToken: customerAccessToken, applePayMerchantIdentifier: merchantIdentifier, applyPayContactFields: contactFields, - supportedShippingCountries: supportedShippingCountries, - resolve: { _ in expectation.fulfill() }, - reject: { _, _, _ in } - ) - - wait(for: [expectation], timeout: 2) + supportedShippingCountries: supportedShippingCountries + ).boolValue } func testConfigureAcceleratedCheckoutsSetsSharedConfigsOnIOS16() throws { @@ -98,58 +93,23 @@ class AcceleratedCheckouts_SupportedTests: XCTestCase { } func testIsAcceleratedCheckoutAvailableBeforeAndAfterConfig() throws { - let beforeExpectation = expectation(description: "isAcceleratedCheckoutAvailable before") - var beforeValue: Bool = true - shopifyCheckoutSheetKit.isAcceleratedCheckoutAvailable({ value in - beforeValue = (value as? Bool) ?? true - beforeExpectation.fulfill() - }, reject: { _, _, _ in }) - wait(for: [beforeExpectation], timeout: 2) - XCTAssertEqual(beforeValue, false) + XCTAssertEqual(shopifyCheckoutSheetKit.isAcceleratedCheckoutAvailable().boolValue, false) configureAcceleratedCheckouts(includeApplePay: false) - let afterExpectation = expectation(description: "isAcceleratedCheckoutAvailable after") - var afterValue: Bool = false - shopifyCheckoutSheetKit.isAcceleratedCheckoutAvailable({ value in - afterValue = (value as? Bool) ?? false - afterExpectation.fulfill() - }, reject: { _, _, _ in }) - wait(for: [afterExpectation], timeout: 2) - XCTAssertEqual(afterValue, true) + XCTAssertEqual(shopifyCheckoutSheetKit.isAcceleratedCheckoutAvailable().boolValue, true) } func testIsApplePayAvailableRequiresApplePayConfig() throws { - let beforeExpectation = expectation(description: "isApplePayAvailable before") - var beforeValue: Bool = true - shopifyCheckoutSheetKit.isApplePayAvailable({ value in - beforeValue = (value as? Bool) ?? true - beforeExpectation.fulfill() - }, reject: { _, _, _ in }) - wait(for: [beforeExpectation], timeout: 2) - XCTAssertEqual(beforeValue, false) + XCTAssertEqual(shopifyCheckoutSheetKit.isApplePayAvailable().boolValue, false) configureAcceleratedCheckouts(includeApplePay: false) - let withoutApplePayExpectation = expectation(description: "isApplePayAvailable without Apple Pay") - var withoutApplePayValue: Bool = true - shopifyCheckoutSheetKit.isApplePayAvailable({ value in - withoutApplePayValue = (value as? Bool) ?? true - withoutApplePayExpectation.fulfill() - }, reject: { _, _, _ in }) - wait(for: [withoutApplePayExpectation], timeout: 2) - XCTAssertEqual(withoutApplePayValue, false) + XCTAssertEqual(shopifyCheckoutSheetKit.isApplePayAvailable().boolValue, false) configureAcceleratedCheckouts(includeApplePay: true) - let afterExpectation = expectation(description: "isApplePayAvailable after") - var afterValue: Bool = false - shopifyCheckoutSheetKit.isApplePayAvailable({ value in - afterValue = (value as? Bool) ?? false - afterExpectation.fulfill() - }, reject: { _, _, _ in }) - wait(for: [afterExpectation], timeout: 2) - XCTAssertEqual(afterValue, true) + XCTAssertEqual(shopifyCheckoutSheetKit.isApplePayAvailable().boolValue, true) } func testConfigureAcceleratedCheckoutsStoresCustomerAccessToken() throws { @@ -395,14 +355,11 @@ class AcceleratedCheckouts_SupportedTests: XCTestCase { XCTAssertTrue(PayWithApplePayButtonLabel.from("unknown", fallback: .buy) == .buy) } - func testConfigureAcceleratedCheckoutsResolvesFalseForInvalidApplePayContactField() throws { - let expectation = self.expectation(description: "configureAcceleratedCheckouts invalid contact field resolves false") - var resolved: Bool = true - + func testConfigureAcceleratedCheckoutsReturnsFalseForInvalidApplePayContactField() throws { let storefrontDomain = "example.myshopify.com" let accessToken = "shpat_test_token" - shopifyCheckoutSheetKit.configureAcceleratedCheckouts( + let resolved = shopifyCheckoutSheetKit.configureAcceleratedCheckouts( storefrontDomain, storefrontAccessToken: accessToken, customerEmail: nil, @@ -410,15 +367,9 @@ class AcceleratedCheckouts_SupportedTests: XCTestCase { customerAccessToken: nil, applePayMerchantIdentifier: "merchant.com.shopify.reactnative.tests", applyPayContactFields: ["email", "not_a_field"], - supportedShippingCountries: [], - resolve: { value in - resolved = (value as? Bool) ?? true - expectation.fulfill() - }, - reject: { _, _, _ in } - ) - - wait(for: [expectation], timeout: 2) + supportedShippingCountries: [] + ).boolValue + XCTAssertEqual(resolved, false) } } diff --git a/sample/ios/ReactNativeTests/AcceleratedCheckouts_UnsupportedTests.swift b/sample/ios/ReactNativeTests/AcceleratedCheckouts_UnsupportedTests.swift index 2c5d7d67..48db5837 100644 --- a/sample/ios/ReactNativeTests/AcceleratedCheckouts_UnsupportedTests.swift +++ b/sample/ios/ReactNativeTests/AcceleratedCheckouts_UnsupportedTests.swift @@ -48,18 +48,7 @@ class AcceleratedCheckouts_UnsupportedTests: XCTestCase { } func testAvailabilityAPIsReturnFalseOnPreIOS16() throws { - let accelExpectation = expectation(description: "isAcceleratedCheckoutAvailable false on <16") - module.isAcceleratedCheckoutAvailable({ value in - XCTAssertEqual(value as? Bool, false) - accelExpectation.fulfill() - }, reject: { _, _, _ in }) - - let applePayExpectation = expectation(description: "isApplePayAvailable false on <16") - module.isApplePayAvailable({ value in - XCTAssertEqual(value as? Bool, false) - applePayExpectation.fulfill() - }, reject: { _, _, _ in }) - - wait(for: [accelExpectation, applePayExpectation], timeout: 2) + XCTAssertEqual(module.isAcceleratedCheckoutAvailable().boolValue, false) + XCTAssertEqual(module.isApplePayAvailable().boolValue, false) } } diff --git a/sample/ios/ReactNativeTests/ShopifyCheckoutSheetKitTests.swift b/sample/ios/ReactNativeTests/ShopifyCheckoutSheetKitTests.swift index 1a246d0e..2f4708c1 100644 --- a/sample/ios/ReactNativeTests/ShopifyCheckoutSheetKitTests.swift +++ b/sample/ios/ReactNativeTests/ShopifyCheckoutSheetKitTests.swift @@ -54,8 +54,7 @@ class ShopifyCheckoutSheetKitTests: XCTestCase { /// getConfig func testReturnsDefaultConfig() { // Call getConfig and capture the result - var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + let result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] // Verify that getConfig returned the expected result XCTAssertEqual(result?["preloading"] as? Bool, true) @@ -165,7 +164,7 @@ class ShopifyCheckoutSheetKitTests: XCTestCase { // Call getConfig and capture the result var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] // Verify that getConfig returned the close button color XCTAssertNotNil(result?["closeButtonColor"]) @@ -260,14 +259,14 @@ class ShopifyCheckoutSheetKitTests: XCTestCase { shopifyCheckoutSheetKit.setConfig(configuration) var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] XCTAssertEqual(result?["logLevel"] as? String, "debug") } func testGetConfigReturnsDefaultLogLevel() { var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] XCTAssertEqual(result?["logLevel"] as? String, "error") } @@ -279,7 +278,7 @@ class ShopifyCheckoutSheetKitTests: XCTestCase { shopifyCheckoutSheetKit.setConfig(configuration) var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] XCTAssertEqual(result?["logLevel"] as? String, "debug") } @@ -291,7 +290,7 @@ class ShopifyCheckoutSheetKitTests: XCTestCase { shopifyCheckoutSheetKit.setConfig(configuration) var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] XCTAssertEqual(result?["logLevel"] as? String, "error") } @@ -303,7 +302,7 @@ class ShopifyCheckoutSheetKitTests: XCTestCase { shopifyCheckoutSheetKit.setConfig(configuration) var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] XCTAssertEqual(result?["logLevel"] as? String, "error") } @@ -315,7 +314,7 @@ class ShopifyCheckoutSheetKitTests: XCTestCase { shopifyCheckoutSheetKit.setConfig(configuration) var result: [String: Any]? - shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + result = shopifyCheckoutSheetKit.getConfig() as? [String: Any] XCTAssertEqual(result?["logLevel"] as? String, "error") }