BoltsTests/AppLinkTests.m (872 lines of code) (raw):

// // AppLinkTests.m // Bolts // // Created by David Poll on 3/10/14. // Copyright (c) 2014 Parse Inc. All rights reserved. // @import XCTest; @import UIKit; @import ObjectiveC.runtime; #import <Bolts/Bolts.h> static NSMutableArray *openedUrls; @interface AppLinkTests : XCTestCase @end @implementation AppLinkTests - (NSString *)stringByEscapingQueryString:(NSString *)string { return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)string, NULL, (CFStringRef)@":/?#[]@!$&'()*+,;=", kCFStringEncodingUTF8)); } - (NSURL *)dataUrlForHtml:(NSString *)html { NSString *encoded = [self stringByEscapingQueryString:html]; NSString *urlString = [NSString stringWithFormat:@"data:text/html,%@", encoded]; return [NSURL URLWithString:urlString]; } /*! Swizzled-in replacement for UIApplication openUrl so that we can capture results. */ - (BOOL)openURLReplacement:(NSURL *)url { if ([url.absoluteString hasPrefix:@"bolts://"] || [url.absoluteString hasPrefix:@"bolts2://"] || [url.absoluteString hasPrefix:@"http://"] || [url.absoluteString hasPrefix:@"file://"]) { [openedUrls addObject:url]; return YES; } return NO; } /*! Produces HTML with meta tags using the keys and values from the content dictionaries of the array as the property and content, respectively. */ - (NSString *)htmlWithMetaTags:(NSArray *)tags { NSMutableString *html = [NSMutableString stringWithString:@"<html><head>"]; for (NSDictionary *dict in tags) { for (NSString *key in dict) { if (dict[key] == [NSNull null]) { [html appendFormat:@"<meta property=\"%@\">", key]; } else { [html appendFormat:@"<meta property=\"%@\" content=\"%@\">", key, dict[key]]; } } } [html appendString:@"</head><body>Hello, world!</body></html>"]; return html; } - (void)waitForTaskOnMainThread:(BFTask *)task { while (!task.isCompleted) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } } - (void)setUp { [super setUp]; openedUrls = [NSMutableArray array]; // Swizzle the openUrl method so we can inspect its usage. Method originalMethod = class_getInstanceMethod([UIApplication class], @selector(openURL:)); Method newMethod = class_getInstanceMethod([self class], @selector(openURLReplacement:)); method_exchangeImplementations(originalMethod, newMethod); } - (void)tearDown { // Un-swizzle openUrl. Method originalMethod = class_getInstanceMethod([UIApplication class], @selector(openURL:)); Method newMethod = class_getInstanceMethod([self class], @selector(openURLReplacement:)); method_exchangeImplementations(originalMethod, newMethod); openedUrls = nil; [super tearDown]; } #pragma mark openURL parsing - (void)testSimpleOpenedURL { NSURL *url = [NSURL URLWithString:@"http://www.example.com"]; BFURL *openedUrl = [BFURL URLWithURL:url]; XCTAssertEqualObjects(url, openedUrl.targetURL); XCTAssertEqualObjects(openedUrl.targetURL, openedUrl.inputURL); XCTAssertEqual((NSUInteger)0, openedUrl.targetQueryParameters.count); XCTAssertEqual((NSUInteger)0, openedUrl.inputQueryParameters.count); } - (void)testOpenedURLWithQueryParameters { NSURL *url = [NSURL URLWithString:@"http://www.example.com?foo&bar=baz&space=%20"]; BFURL *openedUrl = [BFURL URLWithURL:url]; XCTAssertEqualObjects(url, openedUrl.targetURL); XCTAssertEqualObjects(openedUrl.targetURL, openedUrl.inputURL); XCTAssertEqual((NSUInteger)3, openedUrl.targetQueryParameters.count); XCTAssertEqual((NSUInteger)3, openedUrl.inputQueryParameters.count); XCTAssertEqualObjects([NSNull null], openedUrl.targetQueryParameters[@"foo"]); XCTAssertEqualObjects(@"baz", openedUrl.targetQueryParameters[@"bar"]); XCTAssertEqualObjects(@" ", openedUrl.targetQueryParameters[@"space"]); } - (void)testOpenedURLWithBlankQuery { NSURL *url = [NSURL URLWithString:@"http://www.example.com?"]; BFURL *openedUrl = [BFURL URLWithURL:url]; XCTAssertEqualObjects(url, openedUrl.targetURL); XCTAssertEqualObjects(openedUrl.targetURL, openedUrl.inputURL); XCTAssertEqual((NSUInteger)0, openedUrl.targetQueryParameters.count); XCTAssertEqual((NSUInteger)0, openedUrl.inputQueryParameters.count); } - (void)testOpenedURLWithAppLink { NSURL *url = [NSURL URLWithString:@"bolts://?al_applink_data=%7B%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%22%7D"]; BFURL *openedURL = [BFURL URLWithURL:url]; XCTAssertEqualObjects(@"http://www.example.com/path", openedURL.targetURL.absoluteString); XCTAssert(openedURL.appLinkData[@"user_agent"]); XCTAssertEqualObjects(url.absoluteString, openedURL.inputURL.absoluteString); } - (void)testOpenedURLWithAppLinkTargetHasQueryParameters { NSURL *url = [NSURL URLWithString:@"bolts://?al_applink_data=%7B%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%3Ffoo%3Dbar%22%7D"]; BFURL *openedURL = [BFURL URLWithURL:url]; XCTAssertEqualObjects(@"http://www.example.com/path?foo=bar", openedURL.targetURL.absoluteString); XCTAssertEqualObjects(@"bar", openedURL.targetQueryParameters[@"foo"]); XCTAssert(openedURL.appLinkData[@"user_agent"]); XCTAssertEqualObjects(url.absoluteString, openedURL.inputURL.absoluteString); } - (void)testOpenedURLWithAppLinkTargetAndLinkURLHasQueryParameters { NSURL *url = [NSURL URLWithString:@"bolts://?foo=bar&al_applink_data=%7B%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%3Fbaz%3Dbat%22%7D"]; BFURL *openedURL = [BFURL URLWithURL:url]; XCTAssertEqualObjects(@"http://www.example.com/path?baz=bat", openedURL.targetURL.absoluteString); XCTAssertEqualObjects(@"bat", openedURL.targetQueryParameters[@"baz"]); XCTAssertEqualObjects(@"bar", openedURL.inputQueryParameters[@"foo"]); XCTAssert(openedURL.appLinkData[@"user_agent"]); XCTAssertEqualObjects(url.absoluteString, openedURL.inputURL.absoluteString); } - (void)testOpenedURLWithAppLinkWithCustomAppLinkData { NSURL *url = [NSURL URLWithString:@"bolts://?foo=bar&al_applink_data=%7B%22a%22%3A%22b%22%2C%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%3Fbaz%3Dbat%22%7D"]; BFURL *openedURL = [BFURL URLWithURL:url]; XCTAssertEqualObjects(@"http://www.example.com/path?baz=bat", openedURL.targetURL.absoluteString); XCTAssertEqualObjects(@"bat", openedURL.targetQueryParameters[@"baz"]); XCTAssertEqualObjects(@"bar", openedURL.inputQueryParameters[@"foo"]); XCTAssertEqualObjects(@"b", openedURL.appLinkData[@"a"]); XCTAssert(openedURL.appLinkData[@"user_agent"]); XCTAssertEqualObjects(url.absoluteString, openedURL.inputURL.absoluteString); } - (void)testOpenedURLWithBadTarget { NSURL *url = [NSURL URLWithString:@"bolts://?al_applink_data=%7B%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3Anull%7D"]; BFURL *openedURL = [BFURL URLWithURL:url]; XCTAssertEqualObjects(url, openedURL.targetURL); XCTAssert(openedURL.appLinkData[@"user_agent"]); XCTAssertEqualObjects(url.absoluteString, openedURL.inputURL.absoluteString); } - (void)testOpenedIncomingURLWithAppLinkWillPostEvent { NSURL *url = [NSURL URLWithString:@"bolts://?foo=bar&al_applink_data=%7B%22a%22%3A%22b%22%2C%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%3Fbaz%3Dbat%22%7D"]; NSString *sourceApplication = @"com.example.referer"; __block BOOL notificationSent = false; id <NSObject> observationToken = [[NSNotificationCenter defaultCenter] addObserverForName:BFMeasurementEventNotificationName object:nil queue:nil usingBlock:^(NSNotification *note) { NSDictionary *event = note.userInfo; NSDictionary *eventData = event[BFMeasurementEventArgsKey]; if ([@"al_link_parse" isEqualToString:event[BFMeasurementEventNameKey]]) { XCTAssertEqualObjects(@"0", eventData[@"forRenderBackToReferrerBar"]); return; } notificationSent = true; XCTAssertEqualObjects(@"al_nav_in", event[BFMeasurementEventNameKey]); XCTAssertEqualObjects(@"com.example.referer", eventData[@"sourceApplication"]); XCTAssertEqualObjects([url absoluteString], eventData[@"inputURL"]); XCTAssertEqualObjects([url scheme], eventData[@"inputURLScheme"]); }]; [BFURL URLWithInboundURL:url sourceApplication:sourceApplication]; XCTAssertTrue(notificationSent, @"URLWithInboundURL didn't sent notification."); [[NSNotificationCenter defaultCenter] removeObserver:observationToken]; } #pragma mark WebView App Link resolution - (void)testWebViewSimpleAppLinkParsing { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertEqualObjects(url, link.webURL); } - (void)testWebViewAppLinkParsingFailure { BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:[NSURL URLWithString:@"http://badurl"]]; [self waitForTaskOnMainThread:task]; XCTAssertNotNil(task.error); } - (void)testWebViewSimpleAppLinkParsingZeroShouldFallback { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345", @"al:web:should_fallback" : @"0" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertNil(link.webURL); } - (void)testWebViewSimpleAppLinkParsingFalseShouldFallback { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345", @"al:web:should_fallback" : @"fAlse" // case insensitive } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertNil(link.webURL); } - (void)testWebViewSimpleAppLinkParsingWithWebUrl { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345", @"al:web:url" : @"http://www.example.com" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertEqualObjects([NSURL URLWithString:@"http://www.example.com"], link.webURL); } - (void)testWebViewVersionedAppLinkParsing { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" }, @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts2://", @"al:ios:app_name" : @"Bolts2", @"al:ios:app_store_id" : @"67890" }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); target = link.targets[1]; XCTAssertEqualObjects(@"bolts2://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts2", target.appName); XCTAssertEqualObjects(@"67890", target.appStoreId); XCTAssertEqualObjects(url, link.webURL); } - (void)testWebViewVersionedAppLinkParsingOnlyUrls { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios:url" : @"bolts://" }, @{ @"al:ios:url" : @"bolts2://" }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); target = link.targets[1]; XCTAssertEqualObjects(@"bolts2://", target.URL.absoluteString); XCTAssertEqualObjects(url, link.webURL); } - (void)testWebViewVersionedAppLinkParsingUrlsAndNames { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios:url" : @"bolts://" }, @{ @"al:ios:url" : @"bolts2://" }, @{ @"al:ios:app_name" : @"Bolts" }, @{ @"al:ios:app_name" : @"Bolts2" }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); target = link.targets[1]; XCTAssertEqualObjects(@"bolts2://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts2", target.appName); XCTAssertEqualObjects(url, link.webURL); } - (void)testWebViewPlatformFiltering { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" }, @{ @"al:iphone" : [NSNull null] }, @{ @"al:iphone:url" : @"bolts2://iphone", @"al:iphone:app_name" : @"Bolts2", @"al:iphone:app_store_id" : @"67890" }, @{ @"al:ipad" : [NSNull null] }, @{ @"al:ipad:url" : @"bolts2://ipad", @"al:ipad:app_name" : @"Bolts2", @"al:ipad:app_store_id" : @"67890" }, @{ @"al:android" : [NSNull null] }, @{ @"al:android:url" : @"bolts2://android", @"al:android:package" : @"com.bolts2", }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [[BFWebViewAppLinkResolver sharedInstance] appLinkFromURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; // Platform-specific links should be prioritized switch (UI_USER_INTERFACE_IDIOM()) { case UIUserInterfaceIdiomPhone: XCTAssertEqualObjects(@"bolts2://iphone", target.URL.absoluteString); break; case UIUserInterfaceIdiomPad: XCTAssertEqualObjects(@"bolts2://ipad", target.URL.absoluteString); break; #ifdef __TVOS_9_0 case UIUserInterfaceIdiomTV: #endif #ifdef __IPHONE_9_3 case UIUserInterfaceIdiomCarPlay: #endif case UIUserInterfaceIdiomUnspecified: default: break; } XCTAssertEqualObjects(@"Bolts2", target.appName); XCTAssertEqualObjects(@"67890", target.appStoreId); target = link.targets[1]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertEqualObjects(url, link.webURL); } #pragma mark App link meta tag parsing - (void)testSimpleAppLinkParsing { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertEqualObjects(url, link.webURL); } - (void)testAppLinkParsingFailure { BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:[NSURL URLWithString:@"http://badurl"]]; [self waitForTaskOnMainThread:task]; XCTAssertNotNil(task.error); } - (void)testSimpleAppLinkParsingNoShouldFallback { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345", @"al:web:should_fallback" : @"No" // case insensitive } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertNil(link.webURL); } - (void)testSimpleAppLinkParsingFalseShouldFallback { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345", @"al:web:should_fallback" : @"false" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertNil(link.webURL); } - (void)testSimpleAppLinkParsingWithWebUrl { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345", @"al:web:url" : @"http://www.example.com" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)1, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertEqualObjects([NSURL URLWithString:@"http://www.example.com"], link.webURL); } - (void)testVersionedAppLinkParsing { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" }, @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts2://", @"al:ios:app_name" : @"Bolts2", @"al:ios:app_store_id" : @"67890" }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); target = link.targets[1]; XCTAssertEqualObjects(@"bolts2://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts2", target.appName); XCTAssertEqualObjects(@"67890", target.appStoreId); XCTAssertEqualObjects(url, link.webURL); } - (void)testVersionedAppLinkParsingOnlyUrls { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios:url" : @"bolts://" }, @{ @"al:ios:url" : @"bolts2://" }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); target = link.targets[1]; XCTAssertEqualObjects(@"bolts2://", target.URL.absoluteString); XCTAssertEqualObjects(url, link.webURL); } - (void)testVersionedAppLinkParsingUrlsAndNames { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios:url" : @"bolts://" }, @{ @"al:ios:url" : @"bolts2://" }, @{ @"al:ios:app_name" : @"Bolts" }, @{ @"al:ios:app_name" : @"Bolts2" }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); target = link.targets[1]; XCTAssertEqualObjects(@"bolts2://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts2", target.appName); XCTAssertEqualObjects(url, link.webURL); } - (void)testPlatformFiltering { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null] }, @{ @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" }, @{ @"al:iphone" : [NSNull null] }, @{ @"al:iphone:url" : @"bolts2://iphone", @"al:iphone:app_name" : @"Bolts2", @"al:iphone:app_store_id" : @"67890" }, @{ @"al:ipad" : [NSNull null] }, @{ @"al:ipad:url" : @"bolts2://ipad", @"al:ipad:app_name" : @"Bolts2", @"al:ipad:app_store_id" : @"67890" }, @{ @"al:android" : [NSNull null] }, @{ @"al:android:url" : @"bolts2://ipad", @"al:android:package" : @"com.bolts2", }, ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation resolveAppLinkInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLink *link = task.result; XCTAssertEqual((NSUInteger)2, link.targets.count); BFAppLinkTarget *target = link.targets[0]; // Platform-specific links should be prioritized switch (UI_USER_INTERFACE_IDIOM()) { case UIUserInterfaceIdiomPhone: XCTAssertEqualObjects(@"bolts2://iphone", target.URL.absoluteString); break; case UIUserInterfaceIdiomPad: XCTAssertEqualObjects(@"bolts2://ipad", target.URL.absoluteString); break; #ifdef __TVOS_9_0 case UIUserInterfaceIdiomTV: #endif #ifdef __IPHONE_9_3 case UIUserInterfaceIdiomCarPlay: #endif case UIUserInterfaceIdiomUnspecified: default: break; } XCTAssertEqualObjects(@"Bolts2", target.appName); XCTAssertEqualObjects(@"67890", target.appStoreId); target = link.targets[1]; XCTAssertEqualObjects(@"bolts://", target.URL.absoluteString); XCTAssertEqualObjects(@"Bolts", target.appName); XCTAssertEqualObjects(@"12345", target.appStoreId); XCTAssertEqualObjects(url, link.webURL); } #pragma mark App link navigation - (void)testSimpleAppLinkNavigationLookup { BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts://"] appStoreId:@"12345" appName:@"Bolts"]; BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[target] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigationType navigationType = [BFAppLinkNavigation navigationTypeForLink:appLink]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)0, openedUrls.count); // no side effects } - (void)testSimpleAppLinkNavigation { BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts://"] appStoreId:@"12345" appName:@"Bolts"]; BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[ target ] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigationType navigationType = [BFAppLinkNavigation navigateToAppLink:appLink error:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(@"http://www.example.com/path", parsedLink.targetURL.absoluteString); } - (void)testSimpleAppLinkNavigationWithNavigationData { BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts://"] appStoreId:@"12345" appName:@"Bolts"]; BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[ target ] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigation *navigation = [BFAppLinkNavigation navigationWithAppLink:appLink extras:nil appLinkData:@{ @"foo" : @"bar" }]; BFAppLinkNavigationType navigationType = [navigation navigate:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(@"http://www.example.com/path", parsedLink.targetURL.absoluteString); XCTAssertEqualObjects(@"bar", parsedLink.appLinkData[@"foo"]); } - (void)testSimpleAppLinkNavigationWithExtras { BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts://"] appStoreId:@"12345" appName:@"Bolts"]; BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[ target ] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigation *navigation = [BFAppLinkNavigation navigationWithAppLink:appLink extras:@{ @"foo" : @"bar" } appLinkData:nil]; BFAppLinkNavigationType navigationType = [navigation navigate:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(@"http://www.example.com/path", parsedLink.targetURL.absoluteString); XCTAssertEqualObjects(@"bar", parsedLink.appLinkExtras[@"foo"]); } - (void)testSimpleAppLinkNavigationWithExtrasAndNavigationData { BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts://"] appStoreId:@"12345" appName:@"Bolts"]; BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[ target ] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigation *navigation = [BFAppLinkNavigation navigationWithAppLink:appLink extras:@{ @"foo" : @"bar1" } appLinkData:@{ @"foo" : @"bar2" }]; BFAppLinkNavigationType navigationType = [navigation navigate:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(@"http://www.example.com/path", parsedLink.targetURL.absoluteString); XCTAssertEqualObjects(@"bar1", parsedLink.appLinkExtras[@"foo"]); XCTAssertEqualObjects(@"bar2", parsedLink.appLinkData[@"foo"]); } - (void)testAppLinkNavigationMultipleTargetsNoFallback { BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts2://"] appStoreId:@"67890" appName:@"Bolts2"]; BFAppLinkTarget *target2 = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts://"] appStoreId:@"12345" appName:@"Bolts"]; BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[ target, target2 ] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigationType navigationType = [BFAppLinkNavigation navigateToAppLink:appLink error:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(@"http://www.example.com/path", parsedLink.targetURL.absoluteString); XCTAssert([openedUrl.absoluteString hasPrefix:@"bolts2://"]); } - (void)testAppLinkNavigationMultipleTargetsWithFallback { BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts3://"] appStoreId:@"67890" appName:@"Bolts3"]; BFAppLinkTarget *target2 = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:@"bolts://"] appStoreId:@"12345" appName:@"Bolts"]; BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[ target, target2 ] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigationType navigationType = [BFAppLinkNavigation navigateToAppLink:appLink error:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(@"http://www.example.com/path", parsedLink.targetURL.absoluteString); XCTAssert([openedUrl.absoluteString hasPrefix:@"bolts://"]); } - (void)testAppLinkNavigationNoTargets { BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[] webURL:[NSURL URLWithString:@"http://www.example.com/path"]]; BFAppLinkNavigationType navigationType = [BFAppLinkNavigation navigateToAppLink:appLink error:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeBrowser); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedUrl = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(@"http://www.example.com/path", parsedUrl.targetURL.absoluteString); XCTAssertTrue([openedUrl.absoluteString hasPrefix:@"http://www.example.com/path?"]); XCTAssertNotNil(parsedUrl.appLinkData); } - (void)testAppLinkNavigationFailure { BFAppLink *appLink = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:@"http://www.example.com/path"] targets:@[] webURL:nil]; BFAppLinkNavigationType navigationType = [BFAppLinkNavigation navigateToAppLink:appLink error:nil]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeFailure); XCTAssertEqual((NSUInteger)0, openedUrls.count); } #pragma mark App link navigation integration tests - (void)testSimpleAppLinkURLNavigation { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null], @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation navigateToURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLinkNavigationType navigationType = [task.result integerValue]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(url.absoluteString, parsedLink.targetURL.absoluteString); } - (void)testAppLinkURLNavigationMultipleTargetsNoFallback { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null], @"al:ios:url" : @"bolts2://", @"al:ios:app_name" : @"Bolts2", @"al:ios:app_store_id" : @"67890" }, @{ @"al:ios" : [NSNull null], @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation navigateToURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLinkNavigationType navigationType = [task.result integerValue]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(url.absoluteString, parsedLink.targetURL.absoluteString); XCTAssert([openedUrl.absoluteString hasPrefix:@"bolts2://"]); } - (void)testAppLinkURLNavigationMultipleTargetsWithFallback { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null], @"al:ios:url" : @"bolts3://", @"al:ios:app_name" : @"Bolts3", @"al:ios:app_store_id" : @"67890" }, @{ @"al:ios" : [NSNull null], @"al:ios:url" : @"bolts://", @"al:ios:app_name" : @"Bolts", @"al:ios:app_store_id" : @"12345" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation navigateToURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLinkNavigationType navigationType = [task.result integerValue]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeApp); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedLink = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(url.absoluteString, parsedLink.targetURL.absoluteString); XCTAssert([openedUrl.absoluteString hasPrefix:@"bolts://"]); } - (void)testAppLinkURLNavigationNoTargets { NSURL *url = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"html"]; BFTask *task = [BFAppLinkNavigation navigateToURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLinkNavigationType navigationType = [task.result integerValue]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeBrowser); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedUrl = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(url, parsedUrl.targetURL); XCTAssertTrue([openedUrl.absoluteString hasPrefix:url.absoluteString]); XCTAssertNotNil(parsedUrl.appLinkData); } - (void)testAppLinkURLNavigationFallbackToWeb { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:ios" : [NSNull null], @"al:ios:url" : @"bad://", @"al:ios:app_name" : @"Bad", @"al:ios:app_store_id" : @"12345", @"al:web:url" : @"http://www.example.com" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation navigateToURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLinkNavigationType navigationType = [task.result integerValue]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeBrowser); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedUrl = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(url, parsedUrl.targetURL); XCTAssertTrue([openedUrl.absoluteString hasPrefix:@"http://www.example.com?"]); XCTAssertNotNil(parsedUrl.appLinkData); } - (void)testAppLinkURLNavigationWebLinkOnly { NSString *html = [self htmlWithMetaTags:@[ @{ @"al:web:url" : @"http://www.example.com" } ]]; NSURL *url = [self dataUrlForHtml:html]; BFTask *task = [BFAppLinkNavigation navigateToURLInBackground:url]; [self waitForTaskOnMainThread:task]; BFAppLinkNavigationType navigationType = [task.result integerValue]; XCTAssertEqual(navigationType, BFAppLinkNavigationTypeBrowser); XCTAssertEqual((NSUInteger)1, openedUrls.count); NSURL *openedUrl = openedUrls.firstObject; BFURL *parsedUrl = [BFURL URLWithURL:openedUrl]; XCTAssertEqualObjects(url, parsedUrl.targetURL); XCTAssertTrue([openedUrl.absoluteString hasPrefix:@"http://www.example.com?"]); XCTAssertNotNil(parsedUrl.appLinkData); } - (void)testAppLinkToBadUrl { NSURL *url = [NSURL URLWithString:@"http://badurl"]; BFTask *task = [BFAppLinkNavigation navigateToURLInBackground:url]; [self waitForTaskOnMainThread:task]; XCTAssertNotNil(task.error); } @end