FIRIAMDisplayExecutorTests.m 51 KB

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