FIRIAMDisplayExecutorTests.m 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import "FIRIAMActionURLFollower.h"
  19. #import "FIRIAMDisplayExecutor.h"
  20. #import "FIRIAMDisplayTriggerDefinition.h"
  21. #import "FIRIAMMessageContentData.h"
  22. #import "FIRInAppMessaging.h"
  23. // A class implementing protocol FIRIAMMessageContentData to be used for unit testing
  24. @interface FIRIAMMessageContentDataForTesting : NSObject <FIRIAMMessageContentData>
  25. @property(nonatomic, readwrite, nonnull) NSString *titleText;
  26. @property(nonatomic, readwrite, nonnull) NSString *bodyText;
  27. @property(nonatomic, nullable) NSString *actionButtonText;
  28. @property(nonatomic, nullable) NSURL *actionURL;
  29. @property(nonatomic, nullable) NSURL *imageURL;
  30. @property BOOL errorEncountered;
  31. - (instancetype)initWithMessageTitle:(NSString *)title
  32. messageBody:(NSString *)body
  33. actionButtonText:(nullable NSString *)actionButtonText
  34. actionURL:(nullable NSURL *)actionURL
  35. imageURL:(nullable NSURL *)imageURL
  36. hasImageError:(BOOL)hasImageError;
  37. @end
  38. @implementation FIRIAMMessageContentDataForTesting
  39. - (instancetype)initWithMessageTitle:(NSString *)title
  40. messageBody:(NSString *)body
  41. actionButtonText:(nullable NSString *)actionButtonText
  42. actionURL:(nullable NSURL *)actionURL
  43. imageURL:(nullable NSURL *)imageURL
  44. hasImageError:(BOOL)hasImageError {
  45. if (self = [super init]) {
  46. _titleText = title;
  47. _bodyText = body;
  48. _imageURL = imageURL;
  49. _actionButtonText = actionButtonText;
  50. _actionURL = actionURL;
  51. _errorEncountered = hasImageError;
  52. }
  53. return self;
  54. }
  55. - (void)loadImageDataWithBlock:(void (^)(NSData *_Nullable imageData,
  56. NSError *_Nullable error))block {
  57. if (self.errorEncountered) {
  58. block(nil, [NSError errorWithDomain:@"image error" code:0 userInfo:nil]);
  59. } else {
  60. NSString *str = @"image data";
  61. NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
  62. block(data, nil);
  63. }
  64. }
  65. @end
  66. // Defines how the message display component triggers the delegate in unit testing
  67. typedef NS_ENUM(NSInteger, FIRInAppMessagingDelegateInteraction) {
  68. FIRInAppMessagingDelegateInteractionDismiss, // message display component triggers
  69. // messageDismissedWithType:
  70. FIRInAppMessagingDelegateInteractionClick, // message display component triggers
  71. // messageClicked:
  72. FIRInAppMessagingDelegateInteractionError, // message display component triggers
  73. // displayErrorEncountered:
  74. FIRInAppMessagingDelegateInteractionImpressionDetected, // message has finished a valid
  75. // impression, but it's not getting
  76. // closed by the user.
  77. };
  78. // A class implementing protocol FIRInAppMessagingDisplay to be used for unit testing
  79. @interface FIRIAMMessageDisplayForTesting : NSObject <FIRInAppMessagingDisplay>
  80. @property FIRInAppMessagingDelegateInteraction delegateInteraction;
  81. // used for interaction verificatio
  82. @property FIRInAppMessagingDisplayMessage *message;
  83. - (instancetype)initWithDelegateInteraction:(FIRInAppMessagingDelegateInteraction)interaction;
  84. @end
  85. @implementation FIRIAMMessageDisplayForTesting
  86. - (instancetype)initWithDelegateInteraction:(FIRInAppMessagingDelegateInteraction)interaction {
  87. if (self = [super init]) {
  88. _delegateInteraction = interaction;
  89. }
  90. return self;
  91. }
  92. - (void)displayMessage:(FIRInAppMessagingDisplayMessage *)messageForDisplay
  93. displayDelegate:(id<FIRInAppMessagingDisplayDelegate>)displayDelegate {
  94. self.message = messageForDisplay;
  95. switch (self.delegateInteraction) {
  96. case FIRInAppMessagingDelegateInteractionClick:
  97. [displayDelegate messageClicked:messageForDisplay];
  98. break;
  99. case FIRInAppMessagingDelegateInteractionDismiss:
  100. [displayDelegate messageDismissed:messageForDisplay
  101. dismissType:FIRInAppMessagingDismissTypeAuto];
  102. break;
  103. case FIRInAppMessagingDelegateInteractionError:
  104. [displayDelegate displayErrorForMessage:messageForDisplay
  105. error:[NSError errorWithDomain:NSURLErrorDomain
  106. code:0
  107. userInfo:nil]];
  108. break;
  109. case FIRInAppMessagingDelegateInteractionImpressionDetected:
  110. [displayDelegate impressionDetectedForMessage:messageForDisplay];
  111. break;
  112. }
  113. }
  114. @end
  115. @interface FIRInAppMessagingDisplayTestDelegate : NSObject <FIRInAppMessagingDisplayDelegate>
  116. @property(nonatomic) BOOL receivedMessageErrorCallback;
  117. @property(nonatomic) BOOL receivedMessageImpressionCallback;
  118. @property(nonatomic) BOOL receivedMessageClickedCallback;
  119. @property(nonatomic) BOOL receivedMessageDismissedCallback;
  120. @end
  121. @implementation FIRInAppMessagingDisplayTestDelegate
  122. - (void)displayErrorForMessage:(nonnull FIRInAppMessagingDisplayMessage *)inAppMessage
  123. error:(nonnull NSError *)error {
  124. self.receivedMessageErrorCallback = YES;
  125. }
  126. - (void)impressionDetectedForMessage:(nonnull FIRInAppMessagingDisplayMessage *)inAppMessage {
  127. self.receivedMessageImpressionCallback = YES;
  128. }
  129. - (void)messageClicked:(nonnull FIRInAppMessagingDisplayMessage *)inAppMessage {
  130. self.receivedMessageClickedCallback = YES;
  131. }
  132. - (void)messageDismissed:(nonnull FIRInAppMessagingDisplayMessage *)inAppMessage
  133. dismissType:(FIRInAppMessagingDismissType)dismissType {
  134. self.receivedMessageDismissedCallback = YES;
  135. }
  136. @end
  137. @interface FIRIAMDisplayExecutorTests : XCTestCase
  138. @property(nonatomic) FIRIAMDisplaySetting *displaySetting;
  139. @property FIRIAMMessageClientCache *clientMessageCache;
  140. @property id<FIRIAMBookKeeper> mockBookkeeper;
  141. @property id<FIRIAMTimeFetcher> mockTimeFetcher;
  142. @property FIRIAMDisplayExecutor *displayExecutor;
  143. @property FIRIAMActivityLogger *mockActivityLogger;
  144. @property FIRInAppMessaging *mockInAppMessaging;
  145. @property id<FIRIAMAnalyticsEventLogger> mockAnalyticsEventLogger;
  146. @property FIRIAMActionURLFollower *mockActionURLFollower;
  147. @property id<FIRInAppMessagingDisplay> mockMessageDisplayComponent;
  148. // three pre-defined messages
  149. @property FIRIAMMessageDefinition *m1, *m2, *m3, *m4;
  150. @end
  151. @implementation FIRIAMDisplayExecutorTests
  152. - (void)setupMessageTexture {
  153. // startTime, endTime here ensures messages with them are active
  154. NSTimeInterval activeStartTime = 0;
  155. NSTimeInterval activeEndTime = [[NSDate date] timeIntervalSince1970] + 10000;
  156. // m1 & m3 will be of contextual trigger
  157. FIRIAMDisplayTriggerDefinition *contextualTriggerDefinition =
  158. [[FIRIAMDisplayTriggerDefinition alloc] initWithFirebaseAnalyticEvent:@"test_event"];
  159. // m2 and m4 will be of app open trigger
  160. FIRIAMDisplayTriggerDefinition *appOpentriggerDefinition =
  161. [[FIRIAMDisplayTriggerDefinition alloc] initForAppForegroundTrigger];
  162. FIRIAMMessageContentDataForTesting *m1ContentData = [[FIRIAMMessageContentDataForTesting alloc]
  163. initWithMessageTitle:@"m1 title"
  164. messageBody:@"message body"
  165. actionButtonText:nil
  166. actionURL:[NSURL URLWithString:@"http://google.com"]
  167. imageURL:[NSURL URLWithString:@"https://google.com/image"]
  168. hasImageError:NO];
  169. FIRIAMRenderingEffectSetting *renderSetting1 =
  170. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  171. renderSetting1.viewMode = FIRIAMRenderAsBannerView;
  172. FIRIAMMessageRenderData *renderData1 =
  173. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m1"
  174. messageName:@"name"
  175. contentData:m1ContentData
  176. renderingEffect:renderSetting1];
  177. self.m1 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData1
  178. startTime:activeStartTime
  179. endTime:activeEndTime
  180. triggerDefinition:@[ contextualTriggerDefinition ]];
  181. FIRIAMMessageContentDataForTesting *m2ContentData = [[FIRIAMMessageContentDataForTesting alloc]
  182. initWithMessageTitle:@"m2 title"
  183. messageBody:@"message body"
  184. actionButtonText:nil
  185. actionURL:[NSURL URLWithString:@"http://google.com"]
  186. imageURL:[NSURL URLWithString:@"https://unsplash.it/300/400"]
  187. hasImageError:NO];
  188. FIRIAMRenderingEffectSetting *renderSetting2 =
  189. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  190. renderSetting2.viewMode = FIRIAMRenderAsModalView;
  191. FIRIAMMessageRenderData *renderData2 =
  192. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m2"
  193. messageName:@"name"
  194. contentData:m2ContentData
  195. renderingEffect:renderSetting2];
  196. self.m2 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData2
  197. startTime:activeStartTime
  198. endTime:activeEndTime
  199. triggerDefinition:@[ appOpentriggerDefinition ]];
  200. FIRIAMMessageContentDataForTesting *m3ContentData = [[FIRIAMMessageContentDataForTesting alloc]
  201. initWithMessageTitle:@"m3 title"
  202. messageBody:@"message body"
  203. actionButtonText:nil
  204. actionURL:[NSURL URLWithString:@"http://google.com"]
  205. imageURL:[NSURL URLWithString:@"https://google.com/image"]
  206. hasImageError:NO];
  207. FIRIAMRenderingEffectSetting *renderSetting3 =
  208. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  209. renderSetting3.viewMode = FIRIAMRenderAsImageOnlyView;
  210. FIRIAMMessageRenderData *renderData3 =
  211. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m3"
  212. messageName:@"name"
  213. contentData:m3ContentData
  214. renderingEffect:renderSetting3];
  215. self.m3 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData3
  216. startTime:activeStartTime
  217. endTime:activeEndTime
  218. triggerDefinition:@[ contextualTriggerDefinition ]];
  219. FIRIAMMessageContentDataForTesting *m4ContentData = [[FIRIAMMessageContentDataForTesting alloc]
  220. initWithMessageTitle:@"m4 title"
  221. messageBody:@"message body"
  222. actionButtonText:nil
  223. actionURL:[NSURL URLWithString:@"http://google.com"]
  224. imageURL:[NSURL URLWithString:@"https://google.com/image"]
  225. hasImageError:NO];
  226. FIRIAMRenderingEffectSetting *renderSetting4 =
  227. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  228. renderSetting4.viewMode = FIRIAMRenderAsImageOnlyView;
  229. FIRIAMMessageRenderData *renderData4 =
  230. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m4"
  231. messageName:@"name"
  232. contentData:m4ContentData
  233. renderingEffect:renderSetting4];
  234. self.m4 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData4
  235. startTime:activeStartTime
  236. endTime:activeEndTime
  237. triggerDefinition:@[ appOpentriggerDefinition ]];
  238. }
  239. NSTimeInterval DISPLAY_MIN_INTERVALS = 1;
  240. - (void)setUp {
  241. [super setUp];
  242. [self setupMessageTexture];
  243. self.displaySetting = [[FIRIAMDisplaySetting alloc] init];
  244. self.displaySetting.displayMinIntervalInMinutes = DISPLAY_MIN_INTERVALS;
  245. self.mockBookkeeper = OCMProtocolMock(@protocol(FIRIAMBookKeeper));
  246. FIRIAMFetchResponseParser *parser =
  247. [[FIRIAMFetchResponseParser alloc] initWithTimeFetcher:[[FIRIAMTimerWithNSDate alloc] init]];
  248. self.clientMessageCache = [[FIRIAMMessageClientCache alloc] initWithBookkeeper:self.mockBookkeeper
  249. usingResponseParser:parser];
  250. self.mockTimeFetcher = OCMProtocolMock(@protocol(FIRIAMTimeFetcher));
  251. self.mockActivityLogger = OCMClassMock([FIRIAMActivityLogger class]);
  252. self.mockAnalyticsEventLogger = OCMProtocolMock(@protocol(FIRIAMAnalyticsEventLogger));
  253. self.mockInAppMessaging = OCMClassMock([FIRInAppMessaging class]);
  254. self.mockActionURLFollower = OCMClassMock([FIRIAMActionURLFollower class]);
  255. self.displayExecutor =
  256. [[FIRIAMDisplayExecutor alloc] initWithInAppMessaging:self.mockInAppMessaging
  257. setting:self.displaySetting
  258. messageCache:self.clientMessageCache
  259. timeFetcher:self.mockTimeFetcher
  260. bookKeeper:self.mockBookkeeper
  261. actionURLFollower:self.mockActionURLFollower
  262. activityLogger:self.mockActivityLogger
  263. analyticsEventLogger:self.mockAnalyticsEventLogger];
  264. OCMStub([self.mockBookkeeper recordNewImpressionForMessage:[OCMArg any]
  265. withStartTimestampInSeconds:1000]);
  266. }
  267. - (void)testRegularMessageAvailableCase {
  268. // This setup allows next message to be displayed from display interval perspective.
  269. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  270. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  271. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  272. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  273. self.displayExecutor.messageDisplayComponent = display;
  274. [self.clientMessageCache setMessageData:@[ self.m2, self.m4 ]];
  275. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  276. NSInteger remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  277. XCTAssertEqual(1, remainingMsgCount);
  278. // Verify that the message content handed to display component is expected
  279. XCTAssertEqualObjects(self.m2.renderData.messageID, display.message.campaignInfo.messageID);
  280. }
  281. - (void)testFollowingActionURL {
  282. // This setup allows next message to be displayed from display interval perspective.
  283. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  284. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  285. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  286. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  287. self.displayExecutor.messageDisplayComponent = display;
  288. [self.clientMessageCache setMessageData:@[ self.m2 ]];
  289. // not expecting triggering analytics recording
  290. OCMExpect([self.mockActionURLFollower
  291. followActionURL:[OCMArg isEqual:self.m2.renderData.contentData.actionURL]
  292. withCompletionBlock:[OCMArg any]]);
  293. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  294. OCMVerifyAll((id)self.mockActionURLFollower);
  295. }
  296. - (void)testFollowingActionURLForTestMessage {
  297. // This setup allows next message to be displayed from display interval perspective.
  298. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  299. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  300. FIRIAMMessageDefinition *testMessage =
  301. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:self.m1.renderData];
  302. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  303. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  304. self.displayExecutor.messageDisplayComponent = display;
  305. [self.clientMessageCache setMessageData:@[ testMessage ]];
  306. // not expecting triggering analytics recording
  307. OCMExpect([self.mockActionURLFollower
  308. followActionURL:[OCMArg isEqual:testMessage.renderData.contentData.actionURL]
  309. withCompletionBlock:[OCMArg any]]);
  310. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  311. OCMVerifyAll((id)self.mockActionURLFollower);
  312. }
  313. - (void)testClientTestMessageAvailableCase {
  314. // When test message is present in cache, even if the display time interval has not been
  315. // reached, we still render.
  316. // 10 seconds is less than DISPLAY_MIN_INTERVALS minutes, so we have not reached
  317. // minimal display time interval yet.
  318. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(10);
  319. FIRIAMMessageDefinition *testMessage =
  320. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:self.m1.renderData];
  321. [self.clientMessageCache setMessageData:@[ self.m2, testMessage, self.m4 ]];
  322. // We have test message in the cache now.
  323. XCTAssertTrue([self.clientMessageCache hasTestMessage]);
  324. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  325. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  326. self.displayExecutor.messageDisplayComponent = display;
  327. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  328. // No more test message in the cache now.
  329. XCTAssertFalse([self.clientMessageCache hasTestMessage]);
  330. }
  331. // If a message is still being displayed, we won't try to display a second one on top of it
  332. - (void)testNoDualDisplay {
  333. // This setup allows next message to be displayed from display interval perspective.
  334. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  335. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  336. // This display component only detects a valid impression, but does not end the renderig
  337. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  338. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionImpressionDetected];
  339. self.displayExecutor.messageDisplayComponent = display;
  340. [self.clientMessageCache setMessageData:@[ self.m2, self.m4 ]];
  341. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  342. // m2 is being rendered
  343. XCTAssertEqualObjects(self.m2.renderData.messageID, display.message.campaignInfo.messageID);
  344. NSInteger remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  345. XCTAssertEqual(1, remainingMsgCount);
  346. // try to display again when the in-display flag is already turned on (and not turned off yet)
  347. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  348. // Verify that the message in display component is still m2
  349. XCTAssertEqualObjects(self.m2.renderData.messageID, display.message.campaignInfo.messageID);
  350. // message in cache remain unchanged for the second checkAndDisplayNext call
  351. remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  352. XCTAssertEqual(1, remainingMsgCount);
  353. }
  354. // this test case contracts testNoAnalyticsTrackingOnTestMessage to cover both positive
  355. // and negative cases
  356. - (void)testDoesAnalyticsTrackingOnNonTestMessage {
  357. // This setup allows next message to be displayed from display interval perspective.
  358. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  359. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  360. // not expecting triggering analytics recording
  361. OCMExpect([self.mockAnalyticsEventLogger
  362. logAnalyticsEventForType:FIRIAMAnalyticsEventMessageImpression
  363. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  364. withCampaignName:[OCMArg any]
  365. eventTimeInMs:[OCMArg any]
  366. completion:[OCMArg any]]);
  367. [self.clientMessageCache setMessageData:@[ self.m2 ]];
  368. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  369. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  370. self.displayExecutor.messageDisplayComponent = display;
  371. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  372. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  373. }
  374. - (void)testDoesAnalyticsTrackingOnDisplayError {
  375. // 1000 seconds is larger than DISPLAY_MIN_INTERVALS minutes
  376. // last display time is set to 0 by default
  377. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(1000);
  378. // not expecting triggering analytics recording
  379. OCMExpect([self.mockAnalyticsEventLogger
  380. logAnalyticsEventForType:FIRIAMAnalyticsEventImageFetchError
  381. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  382. withCampaignName:[OCMArg any]
  383. eventTimeInMs:[OCMArg any]
  384. completion:[OCMArg any]]);
  385. [self.clientMessageCache setMessageData:@[ self.m2 ]];
  386. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  387. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionError];
  388. self.displayExecutor.messageDisplayComponent = display;
  389. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  390. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  391. }
  392. - (void)testAnalyticsTrackingOnMessageDismissCase {
  393. // This setup allows next message to be displayed from display interval perspective.
  394. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  395. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  396. // not expecting triggering analytics recording
  397. OCMExpect([self.mockAnalyticsEventLogger
  398. logAnalyticsEventForType:FIRIAMAnalyticsEventMessageDismissAuto
  399. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  400. withCampaignName:[OCMArg any]
  401. eventTimeInMs:[OCMArg any]
  402. completion:[OCMArg any]]);
  403. // Make sure we don't log the url follow event.
  404. OCMReject([self.mockAnalyticsEventLogger
  405. logAnalyticsEventForType:FIRIAMAnalyticsEventActionURLFollow
  406. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  407. withCampaignName:[OCMArg any]
  408. eventTimeInMs:[OCMArg any]
  409. completion:[OCMArg any]]);
  410. [self.clientMessageCache setMessageData:@[ self.m2 ]];
  411. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  412. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionDismiss];
  413. self.displayExecutor.messageDisplayComponent = display;
  414. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  415. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  416. }
  417. - (void)testAnalyticsTrackingOnMessageClickCase {
  418. // This setup allows next message to be displayed from display interval perspective.
  419. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  420. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  421. // We expect two analytics events for a click action:
  422. // An impression event and an action URL follow event
  423. OCMExpect([self.mockAnalyticsEventLogger
  424. logAnalyticsEventForType:FIRIAMAnalyticsEventMessageImpression
  425. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  426. withCampaignName:[OCMArg any]
  427. eventTimeInMs:[OCMArg any]
  428. completion:[OCMArg any]]);
  429. OCMExpect([self.mockAnalyticsEventLogger
  430. logAnalyticsEventForType:FIRIAMAnalyticsEventActionURLFollow
  431. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  432. withCampaignName:[OCMArg any]
  433. eventTimeInMs:[OCMArg any]
  434. completion:[OCMArg any]]);
  435. [self.clientMessageCache setMessageData:@[ self.m2 ]];
  436. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  437. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  438. self.displayExecutor.messageDisplayComponent = display;
  439. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  440. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  441. }
  442. - (void)testAnalyticsTrackingOnTestMessageClickCase {
  443. // 1000 seconds is larger than DISPLAY_MIN_INTERVALS minutes
  444. // last display time is set to 0 by default
  445. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(1000);
  446. // We expect two analytics events for a click action:
  447. // An test message impression event and a test message click event
  448. OCMExpect([self.mockAnalyticsEventLogger
  449. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
  450. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  451. withCampaignName:[OCMArg any]
  452. eventTimeInMs:[OCMArg any]
  453. completion:[OCMArg any]]);
  454. OCMExpect([self.mockAnalyticsEventLogger
  455. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageClick
  456. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  457. withCampaignName:[OCMArg any]
  458. eventTimeInMs:[OCMArg any]
  459. completion:[OCMArg any]]);
  460. FIRIAMMessageDefinition *testMessage =
  461. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:self.m2.renderData];
  462. [self.clientMessageCache setMessageData:@[ testMessage ]];
  463. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  464. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  465. self.displayExecutor.messageDisplayComponent = display;
  466. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  467. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  468. }
  469. - (void)testAnalyticsTrackingOnTestMessageDismissCase {
  470. // 1000 seconds is larger than DISPLAY_MIN_INTERVALS minutes
  471. // last display time is set to 0 by default
  472. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(1000);
  473. // We expect a test message impression
  474. OCMExpect([self.mockAnalyticsEventLogger
  475. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageImpression
  476. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  477. withCampaignName:[OCMArg any]
  478. eventTimeInMs:[OCMArg any]
  479. completion:[OCMArg any]]);
  480. // No click event
  481. OCMReject([self.mockAnalyticsEventLogger
  482. logAnalyticsEventForType:FIRIAMAnalyticsEventTestMessageClick
  483. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  484. withCampaignName:[OCMArg any]
  485. eventTimeInMs:[OCMArg any]
  486. completion:[OCMArg any]]);
  487. FIRIAMMessageDefinition *testMessage =
  488. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:self.m2.renderData];
  489. [self.clientMessageCache setMessageData:@[ testMessage ]];
  490. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  491. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionDismiss];
  492. self.displayExecutor.messageDisplayComponent = display;
  493. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  494. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  495. }
  496. - (void)testAnalyticsTrackingImpressionOnValidImpressionDetectedCase {
  497. // This setup allows next message to be displayed from display interval perspective.
  498. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  499. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  500. // not expecting triggering analytics recording
  501. OCMExpect([self.mockAnalyticsEventLogger
  502. logAnalyticsEventForType:FIRIAMAnalyticsEventMessageImpression
  503. forCampaignID:[OCMArg isEqual:self.m2.renderData.messageID]
  504. withCampaignName:[OCMArg any]
  505. eventTimeInMs:[OCMArg any]
  506. completion:[OCMArg any]]);
  507. [self.clientMessageCache setMessageData:@[ self.m2 ]];
  508. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  509. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionImpressionDetected];
  510. self.displayExecutor.messageDisplayComponent = display;
  511. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  512. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  513. }
  514. - (void)testNoAnalyticsTrackingOnTestMessage {
  515. // This setup allows next message to be displayed from display interval perspective.
  516. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  517. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  518. FIRIAMMessageDefinition *testMessage =
  519. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:self.m1.renderData];
  520. // not expecting triggering analytics recording
  521. OCMReject([self.mockAnalyticsEventLogger
  522. logAnalyticsEventForType:FIRIAMAnalyticsEventMessageImpression
  523. forCampaignID:[OCMArg isEqual:self.m1.renderData.messageID]
  524. withCampaignName:[OCMArg any]
  525. eventTimeInMs:[OCMArg any]
  526. completion:[OCMArg any]]);
  527. [self.clientMessageCache setMessageData:@[ testMessage ]];
  528. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  529. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  530. self.displayExecutor.messageDisplayComponent = display;
  531. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  532. OCMVerifyAll((id)self.mockAnalyticsEventLogger);
  533. }
  534. - (void)testNoMessageAvailableCase {
  535. // This setup allows next message to be displayed from display interval perspective.
  536. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  537. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  538. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  539. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  540. self.displayExecutor.messageDisplayComponent = display;
  541. [self.clientMessageCache setMessageData:@[]];
  542. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  543. // No display has happened so the message stored in the display component should be nil
  544. XCTAssertNil(display.message);
  545. NSInteger remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  546. XCTAssertEqual(0, remainingMsgCount);
  547. }
  548. - (void)testIntervalBetweenOnAppOpenDisplays {
  549. self.displaySetting.displayMinIntervalInMinutes = 10;
  550. // last display time is set to 0 by default
  551. // 10 seconds is not long enough for satisfying the 10-min internal requirement
  552. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(10);
  553. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  554. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  555. self.displayExecutor.messageDisplayComponent = display;
  556. [self.clientMessageCache setMessageData:@[ self.m1 ]];
  557. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  558. NSInteger remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  559. // No display has happened so the message stored in the display component should be nil
  560. XCTAssertNil(display.message);
  561. // still got one in the queue
  562. XCTAssertEqual(1, remainingMsgCount);
  563. }
  564. // making sure that we match on the event names for analytics based events
  565. - (void)testOnFirebaseAnalyticsEventDisplayMessages {
  566. // This setup allows next message to be displayed from display interval perspective.
  567. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  568. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  569. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  570. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  571. self.displayExecutor.messageDisplayComponent = display;
  572. // m1 and m3 are messages triggered by 'test_event' analytics events
  573. [self.clientMessageCache setMessageData:@[ self.m1, self.m3 ]];
  574. [self.displayExecutor checkAndDisplayNextContextualMessageForAnalyticsEvent:@"different event"];
  575. NSInteger remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  576. // No message matching event "different event", so no message is nil
  577. XCTAssertNil(display.message);
  578. // still got 2 in the queue
  579. XCTAssertEqual(2, remainingMsgCount);
  580. // now trigger it with 'test_event' and we would expect one message to be displayed and removed
  581. // from cache
  582. [self.displayExecutor checkAndDisplayNextContextualMessageForAnalyticsEvent:@"test_event"];
  583. // Expecting the m1 being used for display
  584. XCTAssertEqualObjects(self.m1.renderData.messageID, display.message.campaignInfo.messageID);
  585. remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  586. // Now only one message remaining in the queue
  587. XCTAssertEqual(1, remainingMsgCount);
  588. }
  589. // no regular message rendering if suppress message display flag is turned on
  590. - (void)testNoRenderingIfMessageDisplayIsSuppressed {
  591. // This setup allows next message to be displayed from display interval perspective.
  592. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  593. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  594. [self.clientMessageCache setMessageData:@[ self.m2, self.m4 ]];
  595. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  596. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  597. self.displayExecutor.messageDisplayComponent = display;
  598. self.displayExecutor.suppressMessageDisplay = YES;
  599. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  600. // no message display has happened
  601. XCTAssertNil(display.message);
  602. NSInteger remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  603. // no message is removed from the cache
  604. XCTAssertEqual(2, remainingMsgCount);
  605. // now allow message rendering again
  606. self.displayExecutor.suppressMessageDisplay = NO;
  607. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  608. NSInteger remainingMsgCount2 = [self.clientMessageCache allRegularMessages].count;
  609. // one message was rendered and removed from the cache
  610. XCTAssertEqual(1, remainingMsgCount2);
  611. }
  612. // No contextual message rendering if suppress message display flag is turned on
  613. - (void)testNoContextualMsgRenderingIfMessageDisplayIsSuppressed {
  614. // This setup allows next message to be displayed from display interval perspective.
  615. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  616. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  617. [self.clientMessageCache setMessageData:@[ self.m1, self.m3 ]];
  618. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  619. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  620. self.displayExecutor.messageDisplayComponent = display;
  621. self.displayExecutor.suppressMessageDisplay = YES;
  622. [self.displayExecutor checkAndDisplayNextContextualMessageForAnalyticsEvent:@"test_event"];
  623. // no message display has happened
  624. XCTAssertNil(display.message);
  625. NSInteger remainingMsgCount = [self.clientMessageCache allRegularMessages].count;
  626. // No message is removed from the cache.
  627. XCTAssertEqual(2, remainingMsgCount);
  628. // now re-enable message rendering again
  629. self.displayExecutor.suppressMessageDisplay = NO;
  630. [self.displayExecutor checkAndDisplayNextContextualMessageForAnalyticsEvent:@"test_event"];
  631. NSInteger remainingMsgCount2 = [self.clientMessageCache allRegularMessages].count;
  632. // one message was rendered and removed from the cache
  633. XCTAssertEqual(1, remainingMsgCount2);
  634. }
  635. - (void)testMessageClickedCallback {
  636. FIRInAppMessagingDisplayTestDelegate *delegate =
  637. [[FIRInAppMessagingDisplayTestDelegate alloc] init];
  638. self.mockInAppMessaging.delegate = delegate;
  639. // This setup allows next message to be displayed from display interval perspective.
  640. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  641. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  642. OCMStub(self.mockInAppMessaging.delegate).andReturn(delegate);
  643. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  644. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionClick];
  645. self.displayExecutor.messageDisplayComponent = display;
  646. [self.clientMessageCache setMessageData:@[ self.m2, self.m4 ]];
  647. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  648. XCTAssertTrue(delegate.receivedMessageClickedCallback);
  649. }
  650. - (void)testMessageImpressionCallback {
  651. FIRInAppMessagingDisplayTestDelegate *delegate =
  652. [[FIRInAppMessagingDisplayTestDelegate alloc] init];
  653. self.mockInAppMessaging.delegate = delegate;
  654. // This setup allows next message to be displayed from display interval perspective.
  655. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  656. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  657. OCMStub(self.mockInAppMessaging.delegate).andReturn(delegate);
  658. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  659. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionImpressionDetected];
  660. self.displayExecutor.messageDisplayComponent = display;
  661. [self.clientMessageCache setMessageData:@[ self.m2, self.m4 ]];
  662. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  663. // Verify that the message content handed to display component is expected
  664. XCTAssertTrue(delegate.receivedMessageImpressionCallback);
  665. }
  666. - (void)testMessageErrorCallback {
  667. FIRInAppMessagingDisplayTestDelegate *delegate =
  668. [[FIRInAppMessagingDisplayTestDelegate alloc] init];
  669. self.mockInAppMessaging.delegate = delegate;
  670. // This setup allows next message to be displayed from display interval perspective.
  671. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  672. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  673. OCMStub(self.mockInAppMessaging.delegate).andReturn(delegate);
  674. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  675. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionError];
  676. self.displayExecutor.messageDisplayComponent = display;
  677. [self.clientMessageCache setMessageData:@[ self.m2, self.m4 ]];
  678. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  679. // Verify that the message content handed to display component is expected
  680. XCTAssertTrue(delegate.receivedMessageErrorCallback);
  681. }
  682. - (void)testMessageDismissedCallback {
  683. FIRInAppMessagingDisplayTestDelegate *delegate =
  684. [[FIRInAppMessagingDisplayTestDelegate alloc] init];
  685. self.mockInAppMessaging.delegate = delegate;
  686. // This setup allows next message to be displayed from display interval perspective.
  687. OCMStub([self.mockTimeFetcher currentTimestampInSeconds])
  688. .andReturn(DISPLAY_MIN_INTERVALS * 60 + 100);
  689. OCMStub(self.mockInAppMessaging.delegate).andReturn(delegate);
  690. FIRIAMMessageDisplayForTesting *display = [[FIRIAMMessageDisplayForTesting alloc]
  691. initWithDelegateInteraction:FIRInAppMessagingDelegateInteractionDismiss];
  692. self.displayExecutor.messageDisplayComponent = display;
  693. [self.clientMessageCache setMessageData:@[ self.m2, self.m4 ]];
  694. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  695. // Verify that the message content handed to display component is expected
  696. XCTAssertTrue(delegate.receivedMessageDismissedCallback);
  697. }
  698. @end