xref: /expo/ios/Tests/Linking/EXLinkingTests.m (revision c8841cb0)
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