FIRIAMDisplayExecutorTests.m 34 KB

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