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
33 changes: 24 additions & 9 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ permissions:
checks: write
id-token: write

env:
XCODE_VERSION: "16.4"
DEVICE: "iPhone 16 Pro"
VERSION: ">=18.0"

jobs:
pr-branch-check-name:
name: "Check PR for semantic branch name"
Expand All @@ -23,20 +28,30 @@ jobs:

test:
name: Test
timeout-minutes: 15
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Xcode 16
uses: actions/checkout@v6

- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 16.3.0
- name: Run Tests
run: >
set -o pipefail &&
xcodebuild test -project mParticle-Rokt.xcodeproj -scheme mParticle_RoktTests -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest'
| xcbeautify --renderer github-actions
shell: bash
xcode-version: ${{ env.XCODE_VERSION }}

- name: Setup specified simulator
uses: futureware-tech/simulator-action@v4
id: simulator
with:
model: ${{ env.DEVICE }}
os: iOS
os_version: ${{ env.VERSION }}
erase_before_boot: true
wait_for_boot: true
shutdown_after_job: true

- name: Run unit tests
run: xcodebuild -project mParticle-Rokt.xcodeproj -scheme mParticle_RoktTests -destination 'id=${{ steps.simulator.outputs.udid }}' test

pr-notify:
if: >
Expand Down
3 changes: 3 additions & 0 deletions mParticle-Rokt-Swift/MPRoktLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public class MPRoktLayout {
confirmUser(attributes: attributes) { identifyCalled in
let preparedAttributes = MPKitRokt.prepareAttributes(attributes, filteredUser: Optional<FilteredMParticleUser>.none, performMapping: true)

// Log custom event for selectPlacements call
MPKitRokt.logSelectPlacementEvent(preparedAttributes)

MPRoktLayout.mpLog("Initializing RoktLayout with arguments sdkTriggered:\(sdkTriggered.wrappedValue), viewName: \(viewName ?? "nil"), locationName:\(locationName), attributes:\(preparedAttributes)")
self.roktLayout = RoktLayout.init(
sdkTriggered: sdkTriggered,
Expand Down
1 change: 1 addition & 0 deletions mParticle-Rokt/MPKitRokt.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@

+ (NSDictionary<NSString *, NSString *> * _Nonnull)prepareAttributes:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes filteredUser:(FilteredMParticleUser * _Nullable)filteredUser performMapping:(BOOL)performMapping;
+ (NSNumber * _Nullable)getRoktHashedEmailUserIdentityType;
+ (void)logSelectPlacementEvent:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes;

@end
11 changes: 11 additions & 0 deletions mParticle-Rokt/MPKitRokt.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
NSString * const kMPPlacementAttributesMapping = @"placementAttributesMapping";
NSString * const kMPHashedEmailUserIdentityType = @"hashedEmailUserIdentityType";
NSString * const kMPRoktEmbeddedViewClassName = @"MPRoktEmbeddedView";
NSString * const kMPEventNameSelectPlacements = @"selectPlacements";
NSInteger const kMPRoktKitCode = 181;

static __weak MPKitRokt *roktKit = nil;
Expand Down Expand Up @@ -104,6 +105,9 @@ - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier
[MPKitRokt MPLog:[NSString stringWithFormat:@"Rokt Kit recieved `executeWithIdentifier` method with the following arguments: \n identifier: %@ \n attributes: %@ \n embeddedViews: %@ \n config: %@ \n callbacks: %@ \n filteredUser identities: %@", identifier, attributes, embeddedViews, mpRoktConfig, callbacks, filteredUser.userIdentities]];
NSDictionary<NSString *, NSString *> *finalAtt = [MPKitRokt prepareAttributes:attributes filteredUser:filteredUser performMapping:NO];

// Log custom event for selectPlacements call
[MPKitRokt logSelectPlacementEvent:finalAtt];

//Convert MPRoktConfig to RoktConfig
RoktConfig *roktConfig = [MPKitRokt convertMPRoktConfig:mpRoktConfig];
NSDictionary<NSString *, RoktEmbeddedView *> *confirmedViews = [self confirmEmbeddedViews:embeddedViews];
Expand Down Expand Up @@ -786,6 +790,13 @@ + (void)MPLog:(NSString *)string {
}
}

+ (void)logSelectPlacementEvent:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes {
MPEvent *event = [[MPEvent alloc] initWithName:kMPEventNameSelectPlacements type:MPEventTypeOther];
event.customAttributes = attributes;
[[MParticle sharedInstance] logEvent:event];
[MPKitRokt MPLog:[NSString stringWithFormat:@"Logged selectplacements custom event with attributes: %@", attributes]];
}


+ (MPRoktEvent * _Nullable)mapEvent:(RoktEvent *)event {
if (!event) {
Expand Down
69 changes: 69 additions & 0 deletions mParticle_RoktTests/mParticle_RoktTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ + (RoktConfig *)convertMPRoktConfig:(MPRoktConfig *)mpRoktConfig;

+ (NSDictionary<NSString *, NSString *> *)transformValuesToString:(NSDictionary<NSString *, id> * _Nullable)originalDictionary;

+ (void)logSelectPlacementEvent:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes;

@end

@interface mParticle_RoktTests : XCTestCase
Expand Down Expand Up @@ -727,4 +729,71 @@ - (void)testGetRoktHashedEmailUserIdentityTypeNil {
[mockMPKitRoktClass stopMocking];
}

#pragma mark - logSelectPlacementEvent tests

- (void)testExecuteWithIdentifierLogsSelectPlacementEventWithPreparedAttributes {
id mockRoktSDK = OCMClassMock([Rokt class]);
id mockMParticleInstance = OCMClassMock([MParticle class]);

// Stub the class method sharedInstance to return our mock
id mockMParticleClass = OCMClassMock([MParticle class]);
OCMStub([mockMParticleClass sharedInstance]).andReturn(mockMParticleInstance);
OCMStub([(MParticle *)mockMParticleInstance environment]).andReturn(MPEnvironmentDevelopment);

NSString *identifier = @"TestView";
NSDictionary *attributes = @{@"email": @"test@example.com"};

// Create a mock user with MPID and identities
FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init];
id mockUser = OCMPartialMock(user);
OCMStub([mockUser userId]).andReturn(@(123456));
OCMStub([mockUser userIdentities]).andReturn(@{@(MPIdentityEmail): @"test@example.com"});
OCMStub([mockUser userAttributes]).andReturn(@{});

// Expect logEvent and verify MPEvent object contains prepared attributes (email, mpid, sandbox)
OCMExpect([(MParticle *)mockMParticleInstance logEvent:[OCMArg checkWithBlock:^BOOL(MPEvent *event) {
// Verify the MPEvent object was created correctly
XCTAssertNotNil(event, @"Event object should not be nil");
XCTAssertEqualObjects(event.name, @"selectPlacements", @"Event name should be 'selectPlacements'");
XCTAssertEqual(event.type, MPEventTypeOther, @"Event type should be MPEventTypeOther");

// Verify custom attributes contain prepared user data
XCTAssertEqualObjects(event.customAttributes[@"email"], @"test@example.com", @"Email should be in attributes");
XCTAssertEqualObjects(event.customAttributes[@"mpid"], @"123456", @"MPID should be in attributes");
XCTAssertNotNil(event.customAttributes[@"sandbox"], @"Sandbox should be in attributes");

return YES;
}]]);

// Stub Rokt execute call
OCMStub([mockRoktSDK executeWithViewName:OCMOCK_ANY
attributes:OCMOCK_ANY
placements:OCMOCK_ANY
config:OCMOCK_ANY
onLoad:OCMOCK_ANY
onUnLoad:OCMOCK_ANY
onShouldShowLoadingIndicator:OCMOCK_ANY
onShouldHideLoadingIndicator:OCMOCK_ANY
onEmbeddedSizeChange:OCMOCK_ANY]);

// Call executeWithIdentifier which triggers logSelectPlacementEvent with prepareAttributes
MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier
attributes:attributes
embeddedViews:nil
config:nil
callbacks:nil
filteredUser:user];

// Verify that logEvent was called with the correct MPEvent object
OCMVerifyAll(mockMParticleInstance);

// Verify execution status
XCTAssertNotNil(status);
XCTAssertEqual(status.returnCode, MPKitReturnCodeSuccess);

[mockRoktSDK stopMocking];
[mockMParticleClass stopMocking];
[mockMParticleInstance stopMocking];
}

@end
33 changes: 33 additions & 0 deletions mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,39 @@ struct mParticle_Rokt_SwiftTests {
#expect(layout.roktLayout != nil, "Layout should handle state changes")
}

// MARK: - SelectPlacements Custom Event Tests

@available(iOS 15, *)
@Test func testPrepareAttributesLogsAttributesEvent() {
// Given
let attributes: [String: String] = ["attr1": "val1"]

// When
let preparedAttributes = MPKitRokt.prepareAttributes(
attributes,
filteredUser: nil,
performMapping: false
)

MPKitRokt.logSelectPlacementEvent(preparedAttributes)

// Then
#expect(preparedAttributes["sandbox"] != nil, "Sandbox attribute should be present")
#expect(preparedAttributes.count >= 1, "Prepared attributes should contain at least the sandbox attribute")
}

@available(iOS 15, *)
@Test func testLogSelectPlacementEventHandlesNilMParticleInstance() {
// Given
let attributes: [String: String] = ["key1": "value1"]

// When
MPKitRokt.logSelectPlacementEvent(attributes)

// Then
#expect(true, "logSelectPlacementEvent should handle MParticle instance state gracefully")
}

// MARK: - Integration Tests

@MainActor @available(iOS 15, *)
Expand Down
Loading