Skip to content

Commit a62b96e

Browse files
ci: Add iOS Integration Style Unit Tests (#338)
1 parent a90c6b2 commit a62b96e

4 files changed

Lines changed: 137 additions & 2 deletions

File tree

.github/workflows/pull-request.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ jobs:
114114
run: ./gradlew assembleDebug
115115

116116
ios-sample-app:
117+
# Keep this display name in sync with branch protection required checks ("iOS Sample App").
117118
name: iOS Sample App
118119
runs-on: macos-15
119120
steps:
@@ -161,7 +162,7 @@ jobs:
161162
working-directory: sample
162163
run: bundle install
163164

164-
- name: Build iOS sample app
165+
- name: Build iOS sample app and run commerce mapping unit tests
165166
working-directory: sample/ios
166167
run: |
167168
bundle exec pod install
@@ -171,7 +172,9 @@ jobs:
171172
-destination 'id=${{ steps.simulator.outputs.udid }}' \
172173
-derivedDataPath ios/build \
173174
-UseModernBuildSystem=YES \
174-
build | bundle exec xcpretty -k
175+
test \
176+
-only-testing:MParticleSampleTests/RCTConvertCommerceMappingTests \
177+
| bundle exec xcpretty -k
175178
176179
pr-notify:
177180
if: >

sample/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,21 @@ From the sample directory:
144144
- `yarn lint` - Run ESLint
145145
- `yarn test` - Run Jest tests
146146

147+
## iOS native unit tests (SDK bridge)
148+
149+
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.
150+
151+
From `sample/ios` after `pod install`:
152+
153+
```bash
154+
xcodebuild -workspace MParticleSample.xcworkspace \
155+
-scheme MParticleSample \
156+
-destination 'platform=iOS Simulator,name=iPhone 16' \
157+
test -only-testing:MParticleSampleTests/RCTConvertCommerceMappingTests
158+
```
159+
160+
Pull requests run these tests in CI (see `.github/workflows/pull-request.yml`).
161+
147162
## Additional Resources
148163

149164
- [mParticle Documentation](https://docs.mparticle.com/)

sample/ios/MParticleSample.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
00E356F31AD99517003FC87E /* MParticleSampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MParticleSampleTests.m */; };
11+
B7C10E912E50AA1100000002 /* RCTConvertCommerceMappingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */; };
1112
0C80B921A6F3F58F76C31292 /* libPods-MParticleSample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-MParticleSample.a */; };
1213
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
1314
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
@@ -31,6 +32,7 @@
3132
00E356EE1AD99517003FC87E /* MParticleSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MParticleSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3233
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3334
00E356F21AD99517003FC87E /* MParticleSampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MParticleSampleTests.m; sourceTree = "<group>"; };
35+
B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTConvertCommerceMappingTests.m; sourceTree = "<group>"; };
3436
13B07F961A680F5B00A75B9A /* MParticleSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MParticleSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
3537
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = MParticleSample/AppDelegate.h; sourceTree = "<group>"; };
3638
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = MParticleSample/AppDelegate.mm; sourceTree = "<group>"; };
@@ -72,6 +74,7 @@
7274
isa = PBXGroup;
7375
children = (
7476
00E356F21AD99517003FC87E /* MParticleSampleTests.m */,
77+
B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */,
7578
00E356F01AD99517003FC87E /* Supporting Files */,
7679
);
7780
path = MParticleSampleTests;
@@ -389,6 +392,7 @@
389392
buildActionMask = 2147483647;
390393
files = (
391394
00E356F31AD99517003FC87E /* MParticleSampleTests.m in Sources */,
395+
B7C10E912E50AA1100000002 /* RCTConvertCommerceMappingTests.m in Sources */,
392396
);
393397
runOnlyForDeploymentPostprocessing = 0;
394398
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#import <XCTest/XCTest.h>
2+
#import <React/RCTConvert.h>
3+
4+
// Match RNMParticle.mm / pod umbrella so tests compile against the same SDK the library uses.
5+
#if defined(__has_include) && __has_include(<mParticle_Apple_SDK_ObjC/mParticle.h>)
6+
#import <mParticle_Apple_SDK_ObjC/mParticle.h>
7+
#elif defined(__has_include) && __has_include(<mParticle_Apple_SDK/mParticle.h>)
8+
#import <mParticle_Apple_SDK/mParticle.h>
9+
#else
10+
#import <mParticle_Apple_SDK_ObjC/mParticle.h>
11+
#endif
12+
13+
// Implemented on `RCTConvert` in `RNMParticle.mm` (react-native-mparticle pod).
14+
@interface RCTConvert (MPCommerceEvent)
15+
+ (MPCommerceEvent *)MPCommerceEvent:(id)json;
16+
+ (MPCommerceEventAction)MPCommerceEventAction:(id)json;
17+
+ (MPPromotionAction)MPPromotionAction:(id)json;
18+
@end
19+
20+
/**
21+
* Guards JS → native commerce enum mapping used by the bridge (including New Architecture).
22+
* Constants must stay aligned with `ProductActionType` / `PromotionActionType` in js/index.tsx.
23+
*
24+
* Direct `MPCommerceEventAction` / `MPPromotionAction` tests above validate the table only.
25+
* JSON → `MPCommerceEvent` tests below exercise the same `+[RCTConvert MPCommerceEvent:]` pipeline
26+
* used to assemble an `MPCommerceEvent` before `-[MParticle logCommerceEvent:]` (legacy bridge path),
27+
* including `MPPromotionContainer:` wiring. That catches regressions such as casting JS ints in
28+
* those helpers instead of calling the mappers. The New Architecture TurboModule `logCommerceEvent`
29+
* codegen struct path is still not invoked here (would require generated C++ types in this target).
30+
*/
31+
@interface RCTConvertCommerceMappingTests : XCTestCase
32+
@end
33+
34+
@implementation RCTConvertCommerceMappingTests
35+
36+
- (void)testMPCommerceEventAction_mapsReactNativeProductActionTypeConstants
37+
{
38+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(1)], MPCommerceEventActionAddToCart);
39+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(2)], MPCommerceEventActionRemoveFromCart);
40+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(3)], MPCommerceEventActionCheckout);
41+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(4)], MPCommerceEventActionCheckoutOptions);
42+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(5)], MPCommerceEventActionClick);
43+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(6)], MPCommerceEventActionViewDetail);
44+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(7)], MPCommerceEventActionPurchase);
45+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(8)], MPCommerceEventActionRefund);
46+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(9)], MPCommerceEventActionAddToWishList);
47+
XCTAssertEqual([RCTConvert MPCommerceEventAction:@(10)], MPCommerceEventActionRemoveFromWishlist);
48+
}
49+
50+
- (void)testMPPromotionAction_mapsReactNativePromotionActionTypeConstants
51+
{
52+
// JS: View = 0, Click = 1. Native: Click = 0, View = 1 (MPPromotion.h).
53+
XCTAssertEqual([RCTConvert MPPromotionAction:@(0)], MPPromotionActionView);
54+
XCTAssertEqual([RCTConvert MPPromotionAction:@(1)], MPPromotionActionClick);
55+
XCTAssertEqual([RCTConvert MPPromotionAction:@(99)], MPPromotionActionClick);
56+
}
57+
58+
#pragma mark - JSON → MPCommerceEvent (integration-style)
59+
60+
- (NSDictionary *)minimalProductJSON
61+
{
62+
return @{
63+
@"name" : @"Test Product",
64+
@"sku" : @"SKU-1",
65+
@"price" : @19.99,
66+
@"quantity" : @1,
67+
@"customAttributes" : @{},
68+
};
69+
}
70+
71+
- (void)testMPCommerceEventFromJSON_productActionFlowsThroughRCTConvertCommerceEvent
72+
{
73+
NSDictionary *json = @{
74+
@"productActionType" : @(7), // Purchase in js/index.tsx
75+
@"products" : @[ [self minimalProductJSON] ],
76+
@"impressions" : @[],
77+
};
78+
79+
MPCommerceEvent *event = [RCTConvert MPCommerceEvent:json];
80+
XCTAssertEqual(event.action, MPCommerceEventActionPurchase);
81+
}
82+
83+
- (void)testMPCommerceEventFromJSON_promotionActionFlowsThroughMPPromotionContainer
84+
{
85+
NSDictionary *promotion = @{
86+
@"id" : @"promo-1",
87+
@"name" : @"Sale",
88+
@"creative" : @"banner",
89+
@"position" : @"home-top",
90+
};
91+
92+
NSDictionary *jsonView = @{
93+
@"promotionActionType" : @(0), // JS PromotionActionType.View
94+
@"promotions" : @[ promotion ],
95+
@"products" : @[],
96+
@"impressions" : @[],
97+
};
98+
MPCommerceEvent *viewEvent = [RCTConvert MPCommerceEvent:jsonView];
99+
XCTAssertNotNil(viewEvent.promotionContainer);
100+
XCTAssertEqual(viewEvent.promotionContainer.action, MPPromotionActionView);
101+
102+
NSDictionary *jsonClick = @{
103+
@"promotionActionType" : @(1), // JS PromotionActionType.Click
104+
@"promotions" : @[ promotion ],
105+
@"products" : @[],
106+
@"impressions" : @[],
107+
};
108+
MPCommerceEvent *clickEvent = [RCTConvert MPCommerceEvent:jsonClick];
109+
XCTAssertNotNil(clickEvent.promotionContainer);
110+
XCTAssertEqual(clickEvent.promotionContainer.action, MPPromotionActionClick);
111+
}
112+
113+
@end

0 commit comments

Comments
 (0)