1/** 2 * Tests deep link transforming/routing methods in the Expo runtime. 3 */ 4 5#import <XCTest/XCTest.h> 6#import "EXKernel.h" 7#import "EXKernelLinkingManager.h" 8 9#pragma mark - private/internal methods in Linking Manager 10 11@interface EXKernelLinkingManager (EXLinkingTests) 12 13+ (BOOL)_isUrl:(NSURL *)urlToRoute deepLinkIntoAppWithManifestUrl:(NSURL *)manifestUrl; 14 15@end 16 17@interface EXLinkingTests : XCTestCase 18 19@end 20 21@implementation EXLinkingTests 22 23- (void)setUp 24{ 25 [super setUp]; 26} 27 28#pragma mark - test link routing 29 30- (void)testIsClientDeepLinkRoutedCorrectly 31{ 32 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin" routesToManifest:@"https://exp.host/@ben/foodwheel"]; 33} 34 35- (void)testIsDeepLinkingInvariantToExpoSchemes 36{ 37 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 38 [self _assertDeepLink:@"http://exp.host/@ben/foodwheel/--/spin" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 39 [self _assertDeepLink:@"exp://exp.host/@ben/foodwheel/--/spin" routesToManifest:@"https://exp.host/@ben/foodwheel"]; 40 [self _assertDeepLink:@"exps://exp.host/@ben/foodwheel/--/spin" routesToManifest:@"https://exp.host/@ben/foodwheel"]; 41} 42 43- (void)testIsDeepLinkingInvariantToReleaseChannelDefault 44{ 45 // deep link contains release channel default, manifest does not specify 46 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?release-channel=default" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 47 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel?release-channel=default" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 48 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/?release-channel=default" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 49 50 // deep link does not specify a release channel, manifest specifies default 51 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel" routesToManifest:@"exp://exp.host/@ben/foodwheel?release-channel=default"]; 52 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin" routesToManifest:@"exp://exp.host/@ben/foodwheel?release-channel=default"]; 53 54 // deep link and manifest both specify default release channel 55 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?release-channel=default" routesToManifest:@"exp://exp.host/@ben/foodwheel?release-channel=default"]; 56} 57 58- (void)testDoesDeepLinkingRespectCustomReleaseChannel 59{ 60 // deep link specifies custom release channel, manifest does not specify, or uses default 61 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel?release-channel=banana" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel"]; 62 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?release-channel=banana" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel"]; 63 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel?release-channel=banana" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel?release-channel=default"]; 64 65 // deep link does not specify a release channel, or uses default; manifest specifies custom 66 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel?release-channel=banana"]; 67 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel?release-channel=banana"]; 68 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?release-channel=default" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel?release-channel=banana"]; 69} 70 71- (void)testDoesDeepLinkingDifferentiateDomains 72{ 73 [self _assertDeepLink:@"https://expo.io/@ben/foodwheel" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel"]; 74 [self _assertDeepLink:@"https://google.com/@ben/foodwheel" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel"]; 75 [self _assertDeepLink:@"https://@ben/foodwheel" doesNotRouteToManifest:@"https://exp.host/@ben/foodwheel"]; 76 [self _assertDeepLink:@"https://expo.io/@ben/foodwheel" doesNotRouteToManifest:@"https://@ben/foodwheel"]; 77} 78 79- (void)testIsDeepLinkingInvariantToQueryString 80{ 81 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel?a=b&c=d" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 82 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel?a=b&c=d" routesToManifest:@"exp://exp.host/@ben/foodwheel?release-channel=default"]; 83 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel?a=b&c=d&release-channel=default" routesToManifest:@"exp://exp.host/@ben/foodwheel?release-channel=default"]; 84 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?a=b&c=d" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 85 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?a=b&c=d" routesToManifest:@"exp://exp.host/@ben/foodwheel?release-channel=default"]; 86 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?a=b&c=d&release-channel=default" routesToManifest:@"exp://exp.host/@ben/foodwheel?release-channel=default"]; 87 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/spin?a=b&c=d&release-channel=banana" doesNotRouteToManifest:@"exp://exp.host/@ben/foodwheel"]; 88} 89 90- (void)testIsDeepLinkingInvariantToDeepLinkPath 91{ 92 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel/--/a/b/c" routesToManifest:@"exp://exp.host/@ben/foodwheel"]; 93 [self _assertDeepLink:@"https://exp.host/@ben/foodwheel" routesToManifest:@"exp://exp.host/@ben/foodwheel/--/a/b/c"]; 94} 95 96- (void)testArePhoneLinksNotDeepLinks 97{ 98 // tel and sms links should never be deep links 99 NSURL *smsUrl = [NSURL URLWithString:@"sms:+604-288-8555"]; 100 NSURL *telUrl = [NSURL URLWithString:@"tel:1-408-555-5555"]; 101 for (NSURL *url in @[ smsUrl, telUrl ]) { 102 XCTAssert([[EXKernel sharedInstance].serviceRegistry.linkingManager linkingModule:nil shouldOpenExpoUrl:url] == NO, 103 @"URL %@ should not be routed internally as a deep link", url); 104 } 105} 106 107- (void)testAreExpoSubdomainsNotDeepLinks 108{ 109 NSURL *docsUrl = [NSURL URLWithString:@"https://docs.expo.io"]; 110 XCTAssert([[EXKernel sharedInstance].serviceRegistry.linkingManager linkingModule:nil shouldOpenExpoUrl:docsUrl] == NO, 111 @"URL %@ should not be routed internally as a deep link", docsUrl); 112} 113 114#pragma mark - test url parsing/transforms 115 116- (void)testIsDeepLinkRemoved 117{ 118 NSString *manifestWithNoDeepLink = @"https://exp.host/@ben/foodwheel/"; 119 NSArray<NSString *> *deepLinks = @[ 120 @"https://exp.host/@ben/foodwheel/--/", 121 @"https://exp.host/@ben/foodwheel/--/spin", 122 @"https://exp.host/@ben/foodwheel/--/a/b/c", 123 @"https://exp.host/@ben/foodwheel/--/spin?a=b&c=d", 124 // @"https://exp.host/@ben/foodwheel?a=b&c=d", // TODO: should this case be supported? 125 ]; 126 for (NSString *deepLink in deepLinks) { 127 NSString *result = [EXKernelLinkingManager stringByRemovingDeepLink:deepLink]; 128 XCTAssert([result isEqualToString:manifestWithNoDeepLink], 129 @"Linking manager should correctly remove the deep link from %@, but instead it returned %@", deepLink, result); 130 } 131} 132 133#pragma mark - EAS manifests 134 135- (void)testEASManifestUrls { 136 [self _assertDeepLink:@"exps://updates.expo.dev/37700852-0840-47b7-80cb-d57746395f57?runtime-version=exposdk%3A40.0.0&channel-name=main" routesToManifest:@"exps://updates.expo.dev/37700852-0840-47b7-80cb-d57746395f57?runtime-version=exposdk%3A40.0.0&channel-name=main"]; 137} 138 139#pragma mark - internal 140 141- (void)_assertDeepLink:(NSString *)deepLinkUrlString routesToManifest:(NSString *)manifestUrlString 142{ 143 NSURL *deepLinkUrl = [NSURL URLWithString:deepLinkUrlString]; 144 NSURL *manifestUrl = [NSURL URLWithString:manifestUrlString]; 145 BOOL result = [EXKernelLinkingManager _isUrl:deepLinkUrl deepLinkIntoAppWithManifestUrl:manifestUrl]; 146 XCTAssert(result, @"Url %@ should deep link to manifest url %@", deepLinkUrl, manifestUrl); 147} 148 149- (void)_assertDeepLink:(NSString *)deepLinkUrlString doesNotRouteToManifest:(NSString *)manifestUrlString 150{ 151 NSURL *deepLinkUrl = [NSURL URLWithString:deepLinkUrlString]; 152 NSURL *manifestUrl = [NSURL URLWithString:manifestUrlString]; 153 BOOL result = [EXKernelLinkingManager _isUrl:deepLinkUrl deepLinkIntoAppWithManifestUrl:manifestUrl]; 154 XCTAssert(!result, @"Url %@ should NOT deep link to manifest url %@", deepLinkUrl, manifestUrl); 155} 156 157@end 158