Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ jobs:
run: ./gradlew assembleDebug

ios-sample-app:
# Keep this display name in sync with branch protection required checks ("iOS Sample App").
name: iOS Sample App
runs-on: macos-15
steps:
Expand Down Expand Up @@ -161,7 +162,7 @@ jobs:
working-directory: sample
run: bundle install

- name: Build iOS sample app
- name: Build iOS sample app and run commerce mapping unit tests
working-directory: sample/ios
run: |
bundle exec pod install
Expand All @@ -171,7 +172,9 @@ jobs:
-destination 'id=${{ steps.simulator.outputs.udid }}' \
-derivedDataPath ios/build \
-UseModernBuildSystem=YES \
build | bundle exec xcpretty -k
test \
-only-testing:MParticleSampleTests/RCTConvertCommerceMappingTests \
| bundle exec xcpretty -k

pr-notify:
if: >
Expand Down
15 changes: 15 additions & 0 deletions sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@ From the sample directory:
- `yarn lint` - Run ESLint
- `yarn test` - Run Jest tests

## iOS native unit tests (SDK bridge)

The sample Xcode project includes **`RCTConvertCommerceMappingTests`**, which asserts that JavaScript `ProductActionType` / `PromotionActionType` integers map to the correct Apple SDK enums, and that **`+[RCTConvert MPCommerceEvent:]`** builds `MPCommerceEvent` / `MPPromotionContainer` with those mappings (the object graph used before `-[MParticle logCommerceEvent:]`) — see comments in that file for scope vs. the TurboModule codegen path.

From `sample/ios` after `pod install`:

```bash
xcodebuild -workspace MParticleSample.xcworkspace \
-scheme MParticleSample \
-destination 'platform=iOS Simulator,name=iPhone 16' \
test -only-testing:MParticleSampleTests/RCTConvertCommerceMappingTests
```

Pull requests run these tests in CI (see `.github/workflows/pull-request.yml`).

## Additional Resources

- [mParticle Documentation](https://docs.mparticle.com/)
Expand Down
4 changes: 4 additions & 0 deletions sample/ios/MParticleSample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* MParticleSampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MParticleSampleTests.m */; };
B7C10E912E50AA1100000002 /* RCTConvertCommerceMappingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */; };
0C80B921A6F3F58F76C31292 /* libPods-MParticleSample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-MParticleSample.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
Expand All @@ -31,6 +32,7 @@
00E356EE1AD99517003FC87E /* MParticleSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MParticleSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* MParticleSampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MParticleSampleTests.m; sourceTree = "<group>"; };
B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTConvertCommerceMappingTests.m; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* MParticleSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MParticleSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = MParticleSample/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = MParticleSample/AppDelegate.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -72,6 +74,7 @@
isa = PBXGroup;
children = (
00E356F21AD99517003FC87E /* MParticleSampleTests.m */,
B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = MParticleSampleTests;
Expand Down Expand Up @@ -389,6 +392,7 @@
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* MParticleSampleTests.m in Sources */,
B7C10E912E50AA1100000002 /* RCTConvertCommerceMappingTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
113 changes: 113 additions & 0 deletions sample/ios/MParticleSampleTests/RCTConvertCommerceMappingTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#import <XCTest/XCTest.h>
#import <React/RCTConvert.h>

// Match RNMParticle.mm / pod umbrella so tests compile against the same SDK the library uses.
#if defined(__has_include) && __has_include(<mParticle_Apple_SDK_ObjC/mParticle.h>)
#import <mParticle_Apple_SDK_ObjC/mParticle.h>
#elif defined(__has_include) && __has_include(<mParticle_Apple_SDK/mParticle.h>)
#import <mParticle_Apple_SDK/mParticle.h>
#else
#import <mParticle_Apple_SDK_ObjC/mParticle.h>
#endif

// Implemented on `RCTConvert` in `RNMParticle.mm` (react-native-mparticle pod).
@interface RCTConvert (MPCommerceEvent)
+ (MPCommerceEvent *)MPCommerceEvent:(id)json;
+ (MPCommerceEventAction)MPCommerceEventAction:(id)json;
+ (MPPromotionAction)MPPromotionAction:(id)json;
@end

/**
* Guards JS → native commerce enum mapping used by the bridge (including New Architecture).
* Constants must stay aligned with `ProductActionType` / `PromotionActionType` in js/index.tsx.
*
* Direct `MPCommerceEventAction` / `MPPromotionAction` tests above validate the table only.
* JSON → `MPCommerceEvent` tests below exercise the same `+[RCTConvert MPCommerceEvent:]` pipeline
* used to assemble an `MPCommerceEvent` before `-[MParticle logCommerceEvent:]` (legacy bridge path),
* including `MPPromotionContainer:` wiring. That catches regressions such as casting JS ints in
* those helpers instead of calling the mappers. The New Architecture TurboModule `logCommerceEvent`
* codegen struct path is still not invoked here (would require generated C++ types in this target).
*/
@interface RCTConvertCommerceMappingTests : XCTestCase
@end

@implementation RCTConvertCommerceMappingTests

- (void)testMPCommerceEventAction_mapsReactNativeProductActionTypeConstants
{
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(1)], MPCommerceEventActionAddToCart);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(2)], MPCommerceEventActionRemoveFromCart);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(3)], MPCommerceEventActionCheckout);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(4)], MPCommerceEventActionCheckoutOptions);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(5)], MPCommerceEventActionClick);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(6)], MPCommerceEventActionViewDetail);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(7)], MPCommerceEventActionPurchase);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(8)], MPCommerceEventActionRefund);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(9)], MPCommerceEventActionAddToWishList);
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(10)], MPCommerceEventActionRemoveFromWishlist);
}

- (void)testMPPromotionAction_mapsReactNativePromotionActionTypeConstants
{
// JS: View = 0, Click = 1. Native: Click = 0, View = 1 (MPPromotion.h).
XCTAssertEqual([RCTConvert MPPromotionAction:@(0)], MPPromotionActionView);
XCTAssertEqual([RCTConvert MPPromotionAction:@(1)], MPPromotionActionClick);
XCTAssertEqual([RCTConvert MPPromotionAction:@(99)], MPPromotionActionClick);
}

#pragma mark - JSON → MPCommerceEvent (integration-style)

- (NSDictionary *)minimalProductJSON
{
return @{
@"name" : @"Test Product",
@"sku" : @"SKU-1",
@"price" : @19.99,
@"quantity" : @1,
@"customAttributes" : @{},
};
}

- (void)testMPCommerceEventFromJSON_productActionFlowsThroughRCTConvertCommerceEvent
{
NSDictionary *json = @{
@"productActionType" : @(7), // Purchase in js/index.tsx
@"products" : @[ [self minimalProductJSON] ],
@"impressions" : @[],
};

MPCommerceEvent *event = [RCTConvert MPCommerceEvent:json];
XCTAssertEqual(event.action, MPCommerceEventActionPurchase);
}

- (void)testMPCommerceEventFromJSON_promotionActionFlowsThroughMPPromotionContainer
{
NSDictionary *promotion = @{
@"id" : @"promo-1",
@"name" : @"Sale",
@"creative" : @"banner",
@"position" : @"home-top",
};

NSDictionary *jsonView = @{
@"promotionActionType" : @(0), // JS PromotionActionType.View
@"promotions" : @[ promotion ],
@"products" : @[],
@"impressions" : @[],
};
MPCommerceEvent *viewEvent = [RCTConvert MPCommerceEvent:jsonView];
XCTAssertNotNil(viewEvent.promotionContainer);
XCTAssertEqual(viewEvent.promotionContainer.action, MPPromotionActionView);

NSDictionary *jsonClick = @{
@"promotionActionType" : @(1), // JS PromotionActionType.Click
@"promotions" : @[ promotion ],
@"products" : @[],
@"impressions" : @[],
};
MPCommerceEvent *clickEvent = [RCTConvert MPCommerceEvent:jsonClick];
XCTAssertNotNil(clickEvent.promotionContainer);
XCTAssertEqual(clickEvent.promotionContainer.action, MPPromotionActionClick);
}

@end
Loading