FIRIAMActionUrlFollowerTests.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /*
  2. * Copyright 2018 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/Runtime/FIRIAMActionURLFollower.h"
  19. // since OCMock does support mocking respondsToSelector on mock object, we have to define
  20. // different delegate classes with different coverages of certain delegate methods:
  21. // FIRIAMActionURLFollower behavior depend on these method implementation coverages on the
  22. // delegate
  23. // this delegate only implements application:continueUserActivity:restorationHandler
  24. @interface Delegate1 : NSObject <UIApplicationDelegate>
  25. - (BOOL)application:(UIApplication *)application
  26. continueUserActivity:(NSUserActivity *)userActivity
  27. restorationHandler:(void (^)(NSArray *))restorationHandler;
  28. @end
  29. @implementation Delegate1
  30. - (BOOL)application:(UIApplication *)application
  31. continueUserActivity:(NSUserActivity *)userActivity
  32. restorationHandler:(void (^)(NSArray *))restorationHandler {
  33. return YES;
  34. }
  35. @end
  36. // this delegate only implements application:openURL:options which is suitable for custom url scheme
  37. // link handling
  38. @interface Delegate2 : NSObject <UIApplicationDelegate>
  39. - (BOOL)application:(UIApplication *)app
  40. openURL:(NSURL *)url
  41. options:(NSDictionary<NSString *, id> *)options;
  42. @end
  43. @implementation Delegate2
  44. - (BOOL)application:(UIApplication *)app
  45. openURL:(NSURL *)url
  46. options:(NSDictionary<NSString *, id> *)options {
  47. return YES;
  48. }
  49. @end
  50. @interface FIRIAMActionURLFollowerTests : XCTestCase
  51. @property FIRIAMActionURLFollower *actionFollower;
  52. @property UIApplication *mockApplication;
  53. @property id<UIApplicationDelegate> mockAppDelegate;
  54. @end
  55. @implementation FIRIAMActionURLFollowerTests
  56. - (void)setUp {
  57. [super setUp];
  58. self.mockApplication = OCMClassMock([UIApplication class]);
  59. }
  60. - (void)tearDown {
  61. // Put teardown code here. This method is called after the invocation of each test method in the
  62. // class.
  63. [super tearDown];
  64. }
  65. - (void)testUniversalLinkHandlingReturnYES {
  66. self.mockAppDelegate = OCMClassMock([Delegate1 class]);
  67. OCMStub([self.mockApplication delegate]).andReturn(self.mockAppDelegate);
  68. // In this test case, app delegate's application:continueUserActivity:restorationHandler
  69. // handles the url and returns YES
  70. NSURL *url = [NSURL URLWithString:@"http://test.com"];
  71. OCMExpect([self.mockAppDelegate application:[OCMArg isKindOfClass:[UIApplication class]]
  72. continueUserActivity:[OCMArg checkWithBlock:^BOOL(id userActivity) {
  73. // verifying the type and url field for the userActivity object
  74. NSUserActivity *activity = (NSUserActivity *)userActivity;
  75. // Use string literal to ensure compatibility with Xcode 26 and iOS 18
  76. NSString *browsingWebType = @"NSUserActivityTypeBrowsingWeb";
  77. return [activity.activityType isEqualToString:browsingWebType] &&
  78. [activity.webpageURL isEqual:url];
  79. }]
  80. restorationHandler:[OCMArg any]])
  81. .andReturn(YES);
  82. FIRIAMActionURLFollower *follower =
  83. [[FIRIAMActionURLFollower alloc] initWithCustomURLSchemeArray:@[]
  84. withApplication:self.mockApplication];
  85. XCTestExpectation *expectation =
  86. [self expectationWithDescription:@"Completion Callback Triggered"];
  87. [follower followActionURL:url
  88. withCompletionBlock:^(BOOL success) {
  89. XCTAssertTrue(success);
  90. [expectation fulfill];
  91. }];
  92. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  93. OCMVerifyAll((id)self.mockAppDelegate);
  94. }
  95. - (void)setupOpenURLViaIOSForUIApplicationWithReturnValue:(BOOL)returnValue {
  96. // it would fallback to either openURL:options:completionHandler:
  97. // on the UIApplication object to follow the url
  98. // id types is needed for calling invokeBlockWithArgs
  99. id yesOrNo = returnValue ? @YES : @NO;
  100. OCMStub([self.mockApplication openURL:[OCMArg any]
  101. options:[OCMArg any]
  102. completionHandler:([OCMArg invokeBlockWithArgs:yesOrNo, nil])]);
  103. }
  104. - (void)testUniversalLinkHandlingReturnNo {
  105. self.mockAppDelegate = OCMClassMock([Delegate1 class]);
  106. OCMStub([self.mockApplication delegate]).andReturn(self.mockAppDelegate);
  107. // In this test case, app delegate's application:continueUserActivity:restorationHandler
  108. // tries to handle the url but returns NO. We should fallback to the do iOS OpenURL for
  109. // this case
  110. NSURL *url = [NSURL URLWithString:@"http://test.com"];
  111. OCMExpect([self.mockAppDelegate application:[OCMArg isKindOfClass:[UIApplication class]]
  112. continueUserActivity:[OCMArg any]
  113. restorationHandler:[OCMArg any]])
  114. .andReturn(NO);
  115. [self setupOpenURLViaIOSForUIApplicationWithReturnValue:YES];
  116. FIRIAMActionURLFollower *follower =
  117. [[FIRIAMActionURLFollower alloc] initWithCustomURLSchemeArray:@[]
  118. withApplication:self.mockApplication];
  119. XCTestExpectation *expectation =
  120. [self expectationWithDescription:@"Completion Callback Triggered"];
  121. [follower followActionURL:url
  122. withCompletionBlock:^(BOOL success) {
  123. [expectation fulfill];
  124. }];
  125. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  126. OCMVerifyAll((id)self.mockAppDelegate);
  127. }
  128. - (void)testCustomSchemeHandlingReturnYES {
  129. self.mockAppDelegate = OCMClassMock([Delegate2 class]);
  130. OCMStub([self.mockApplication delegate]).andReturn(self.mockAppDelegate);
  131. // we support custom url scheme 'scheme1' and 'scheme2' in this setup
  132. FIRIAMActionURLFollower *follower =
  133. [[FIRIAMActionURLFollower alloc] initWithCustomURLSchemeArray:@[ @"scheme1", @"scheme2" ]
  134. withApplication:self.mockApplication];
  135. NSURL *customURL = [NSURL URLWithString:@"scheme1://test.com"];
  136. OCMExpect([self.mockAppDelegate application:[OCMArg isKindOfClass:[UIApplication class]]
  137. openURL:[OCMArg checkWithBlock:^BOOL(id urlId) {
  138. // verifying url received by the app delegate is expected
  139. NSURL *url = (NSURL *)urlId;
  140. return [url isEqual:customURL];
  141. }]
  142. options:[OCMArg any]])
  143. .andReturn(YES);
  144. XCTestExpectation *expectation =
  145. [self expectationWithDescription:@"Completion Callback Triggered"];
  146. [follower followActionURL:customURL
  147. withCompletionBlock:^(BOOL success) {
  148. XCTAssertTrue(success);
  149. [expectation fulfill];
  150. }];
  151. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  152. OCMVerifyAll((id)self.mockAppDelegate);
  153. }
  154. - (void)testCustomSchemeHandlingReturnNO {
  155. self.mockAppDelegate = OCMClassMock([Delegate2 class]);
  156. OCMStub([self.mockApplication delegate]).andReturn(self.mockAppDelegate);
  157. // we support custom url scheme 'scheme1' and 'scheme2' in this setup
  158. FIRIAMActionURLFollower *follower =
  159. [[FIRIAMActionURLFollower alloc] initWithCustomURLSchemeArray:@[ @"scheme1", @"scheme2" ]
  160. withApplication:self.mockApplication];
  161. NSURL *customURL = [NSURL URLWithString:@"scheme1://test.com"];
  162. OCMExpect([self.mockAppDelegate application:[OCMArg isKindOfClass:[UIApplication class]]
  163. openURL:[OCMArg checkWithBlock:^BOOL(id urlId) {
  164. // verifying url received by the app delegate is expected
  165. NSURL *url = (NSURL *)urlId;
  166. return [url isEqual:customURL];
  167. }]
  168. options:[OCMArg any]])
  169. .andReturn(NO);
  170. // it would fallback to Open URL with iOS System
  171. [self setupOpenURLViaIOSForUIApplicationWithReturnValue:NO];
  172. XCTestExpectation *expectation =
  173. [self expectationWithDescription:@"Completion Callback Triggered"];
  174. [follower followActionURL:customURL
  175. withCompletionBlock:^(BOOL success) {
  176. // since both custom scheme url open and fallback iOS url open returns NO, we expect
  177. // to get a NO here
  178. XCTAssertFalse(success);
  179. [expectation fulfill];
  180. }];
  181. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  182. OCMVerifyAll((id)self.mockAppDelegate);
  183. }
  184. - (void)testCustomSchemeNotMatching {
  185. self.mockAppDelegate = OCMClassMock([Delegate2 class]);
  186. OCMStub([self.mockApplication delegate]).andReturn(self.mockAppDelegate);
  187. // we support custom url scheme 'scheme1' and 'scheme2' in this setup
  188. FIRIAMActionURLFollower *follower =
  189. [[FIRIAMActionURLFollower alloc] initWithCustomURLSchemeArray:@[ @"scheme1", @"scheme2" ]
  190. withApplication:self.mockApplication];
  191. NSURL *customURL = [NSURL URLWithString:@"unknown-scheme://test.com"];
  192. // since custom scheme does not match, we should not expect app delegate's open URL method
  193. // being triggered
  194. OCMReject([self.mockAppDelegate application:[OCMArg any]
  195. openURL:[OCMArg any]
  196. options:[OCMArg any]]);
  197. XCTestExpectation *expectation =
  198. [self expectationWithDescription:@"Completion Callback Triggered"];
  199. [self setupOpenURLViaIOSForUIApplicationWithReturnValue:YES];
  200. [follower followActionURL:customURL
  201. withCompletionBlock:^(BOOL success) {
  202. XCTAssertTrue(success);
  203. [expectation fulfill];
  204. }];
  205. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  206. OCMVerifyAll((id)self.mockAppDelegate);
  207. }
  208. - (void)testUniversalLinkWithoutContinueUserActivityDefined {
  209. // Delegate2 does not define application:continueUserActivity:restorationHandler
  210. self.mockAppDelegate = OCMClassMock([Delegate2 class]);
  211. OCMStub([self.mockApplication delegate]).andReturn(self.mockAppDelegate);
  212. FIRIAMActionURLFollower *follower =
  213. [[FIRIAMActionURLFollower alloc] initWithCustomURLSchemeArray:@[]
  214. withApplication:self.mockApplication];
  215. // so for this url, even if it's a http or https link, we should fall back to openURL with
  216. // iOS system
  217. NSURL *url = [NSURL URLWithString:@"http://test.com"];
  218. XCTestExpectation *expectation =
  219. [self expectationWithDescription:@"Completion Callback Triggered"];
  220. [self setupOpenURLViaIOSForUIApplicationWithReturnValue:YES];
  221. [follower followActionURL:url
  222. withCompletionBlock:^(BOOL success) {
  223. XCTAssertTrue(success);
  224. [expectation fulfill];
  225. }];
  226. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  227. }
  228. @end