FIRIAMDisplayExecutorTests.m 44 KB

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