Index: ios/chrome/app/application_delegate/url_opener_unittest.mm |
diff --git a/ios/chrome/app/application_delegate/url_opener_unittest.mm b/ios/chrome/app/application_delegate/url_opener_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..60ff610f053c9dc290f945619a5efa1be28c3e0a |
--- /dev/null |
+++ b/ios/chrome/app/application_delegate/url_opener_unittest.mm |
@@ -0,0 +1,482 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ios/chrome/app/application_delegate/url_opener.h" |
+ |
+#import <Foundation/Foundation.h> |
+ |
+#include "base/mac/scoped_nsobject.h" |
+#include "ios/chrome/app/application_delegate/app_state.h" |
+#include "ios/chrome/app/application_delegate/app_state_testing.h" |
+#include "ios/chrome/app/application_delegate/mock_tab_opener.h" |
+#include "ios/chrome/app/chrome_app_startup_parameters.h" |
+#include "ios/chrome/app/main_application_delegate.h" |
+#include "ios/chrome/app/main_controller.h" |
+#include "ios/chrome/app/main_controller_private.h" |
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/ui/browser_view_controller.h" |
+#import "ios/chrome/test/base/scoped_block_swizzler.h" |
+#import "ios/testing/ocmock_complex_type_helper.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "testing/platform_test.h" |
+#import "third_party/ocmock/OCMock/OCMock.h" |
+#include "third_party/ocmock/gtest_support.h" |
+ |
+#pragma mark - Tab Switcher Mock |
+ |
+// This mocks either a iPad tab switcher controller or a iPhone stack view |
+// controller. |
+@interface URLOpenerOCMockComplexTypeHandler : OCMockComplexTypeHelper |
+@end |
+ |
+@implementation URLOpenerOCMockComplexTypeHandler |
+ |
+typedef Tab* (^mock_gurl_nsuinteger_pagetransition)(const GURL&, |
+ NSUInteger, |
+ ui::PageTransition); |
+ |
+- (Tab*)dismissWithNewTabAnimationToModel:(TabModel*)targetModel |
+ withURL:(const GURL&)url |
+ atIndex:(NSUInteger)position |
+ transition:(ui::PageTransition)transition { |
+ static_cast<mock_gurl_nsuinteger_pagetransition>( |
+ [self blockForSelector:_cmd])(url, position, transition); |
+ id mockTab = [OCMockObject mockForClass:[Tab class]]; |
+ return mockTab; |
+} |
+ |
+- (Tab*)addSelectedTabWithURL:(const GURL&)url |
+ atIndex:(NSUInteger)position |
+ transition:(ui::PageTransition)transition { |
+ static_cast<mock_gurl_nsuinteger_pagetransition>( |
+ [self blockForSelector:_cmd])(url, position, transition); |
+ id mockTab = [OCMockObject mockForClass:[Tab class]]; |
+ return mockTab; |
+} |
+ |
+@end |
+ |
+#pragma mark - BrowserViewController Mock |
+ |
+// Mock BVC class to use for test cases where OCMock gets handled incorrectly |
+// by UIViewController. |
+@interface URLOpenerMockBVC : UIViewController |
+@property(nonatomic, assign) ios::ChromeBrowserState* browserState; |
+@property(nonatomic, assign) GURL tabURL; |
+@property(nonatomic, assign) NSUInteger position; |
+@property(nonatomic, assign) ui::PageTransition transition; |
+ |
+- (Tab*)addSelectedTabWithURL:(const GURL&)url |
+ atIndex:(NSUInteger)position |
+ transition:(ui::PageTransition)transition; |
+- (void)expectNewForegroundTab; |
+- (void)setActive:(BOOL)active; |
+- (TabModel*)tabModel; |
+@end |
+ |
+@implementation URLOpenerMockBVC |
+@synthesize browserState = _browserState; |
+@synthesize tabURL = _tabURL; |
+@synthesize position = _position; |
+@synthesize transition = _transition; |
+ |
+- (Tab*)addSelectedTabWithURL:(const GURL&)url |
+ atIndex:(NSUInteger)position |
+ transition:(ui::PageTransition)transition { |
+ self.tabURL = url; |
+ self.position = position; |
+ self.transition = transition; |
+ return nil; |
+} |
+ |
+- (void)expectNewForegroundTab { |
+ // no-op. |
+} |
+ |
+- (void)setActive:(BOOL)active { |
+ // no-op |
+} |
+ |
+- (void)setPrimary:(BOOL)primary { |
+ // no-op |
+} |
+ |
+- (TabModel*)tabModel { |
+ return nil; |
+} |
+ |
+@end |
+ |
+class URLOpenerTest : public PlatformTest { |
+ protected: |
+ MainController* GetMainController() { |
+ if (!main_controller_.get()) { |
+ main_controller_.reset([[MainController alloc] init]); |
+ [main_controller_ setUpAsForegrounded]; |
+ id mainTabModel = [OCMockObject mockForClass:[TabModel class]]; |
+ [[mainTabModel stub] resetSessionMetrics]; |
+ [[mainTabModel stub] browserStateDestroyed]; |
+ [[mainTabModel stub] addObserver:[OCMArg any]]; |
+ [[mainTabModel stub] removeObserver:[OCMArg any]]; |
+ [[main_controller_ browserViewInformation] setMainTabModel:mainTabModel]; |
+ } |
+ return main_controller_.get(); |
+ } |
+ |
+ private: |
+ base::scoped_nsobject<MainController> main_controller_; |
+}; |
+ |
+TEST_F(URLOpenerTest, HandleOpenURLWithNoOpenTab) { |
+ // The tab switcher controller should be dismissed with a new tab containing |
+ // the external URL. |
+ NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"]; |
+ base::scoped_nsobject<ChromeAppStartupParameters> params( |
+ [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url |
+ fromSourceApplication:nil]); |
+ |
+ base::scoped_nsobject<id> bvcMock([[URLOpenerMockBVC alloc] init]); |
+ |
+ base::scoped_nsobject<id> tabSwitcherController; |
+ tabSwitcherController.reset([[URLOpenerOCMockComplexTypeHandler alloc] |
+ initWithRepresentedObject:[OCMockObject |
+ mockForProtocol:@protocol(UrlLoader)]]); |
+ |
+ base::scoped_nsobject<id> block([(id) ^ (const GURL& url, NSUInteger position, |
+ ui::PageTransition transition) { |
+ EXPECT_EQ(url, [params externalURL]); |
+ EXPECT_EQ(NSNotFound, static_cast<NSInteger>(position)); |
+ EXPECT_TRUE(PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_LINK)); |
+ } copy]); |
+ SEL dismissSelector = |
+ @selector(dismissWithNewTabAnimationToModel:withURL:atIndex:transition:); |
+ [tabSwitcherController onSelector:dismissSelector callBlockExpectation:block]; |
+ |
+ // Setup main controller. |
+ MainController* controller = GetMainController(); |
+ controller.browserViewInformation.mainBVC = bvcMock; |
+ controller.tabSwitcherController = tabSwitcherController; |
+ controller.tabSwitcherActive = YES; |
+ |
+ id mainApplicationDelegate = |
+ [OCMockObject mockForClass:[MainApplicationDelegate class]]; |
+ |
+ AppState* appState = |
+ [[[AppState alloc] initWithBrowserLauncher:controller |
+ startupInformation:controller |
+ applicationDelegate:mainApplicationDelegate |
+ window:controller.window |
+ shouldOpenNTP:YES] autorelease]; |
+ controller.appState = appState; |
+ |
+ NSDictionary<NSString*, id>* options = nil; |
+ [URLOpener openURL:url |
+ applicationActive:YES |
+ options:options |
+ tabOpener:controller |
+ startupInformation:controller]; |
+ |
+ EXPECT_OCMOCK_VERIFY(tabSwitcherController); |
+} |
+ |
+TEST_F(URLOpenerTest, HandleOpenURLWithOpenTabs) { |
+ // The URL should be routed to the main BVC; the OTR BVC shouldn't get calls. |
+ NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"]; |
+ id otrBVCMock = [OCMockObject mockForClass:[BrowserViewController class]]; |
+ base::scoped_nsobject<id> bvcMock([[URLOpenerMockBVC alloc] init]); |
+ TestChromeBrowserState::Builder mainBrowserStateBuilder; |
+ std::unique_ptr<TestChromeBrowserState> chrome_browser_state = |
+ mainBrowserStateBuilder.Build(); |
+ ((URLOpenerMockBVC*)bvcMock).browserState = chrome_browser_state.get(); |
+ |
+ // Setup main controller. |
+ MainController* controller = GetMainController(); |
+ controller.browserViewInformation.mainBVC = bvcMock; |
+ controller.browserViewInformation.otrBVC = otrBVCMock; |
+ |
+ NSDictionary<NSString*, id>* options = nil; |
+ [URLOpener openURL:url |
+ applicationActive:YES |
+ options:options |
+ tabOpener:controller |
+ startupInformation:controller]; |
+ |
+ EXPECT_EQ(GURL("http://www.google.com/"), |
+ [(URLOpenerMockBVC*)bvcMock tabURL]); |
+ EXPECT_EQ(NSNotFound, |
+ static_cast<NSInteger>([(URLOpenerMockBVC*)bvcMock position])); |
+ EXPECT_TRUE(PageTransitionCoreTypeIs([(URLOpenerMockBVC*)bvcMock transition], |
+ ui::PAGE_TRANSITION_LINK)); |
+ EXPECT_OCMOCK_VERIFY(otrBVCMock); |
+} |
+ |
+TEST_F(URLOpenerTest, HandleOpenURL) { |
+ // A set of tests for robustness of |
+ // application:openURL:options:tabOpener:startupInformation: |
+ // It verifies that the function handles correctly differents URL parsed by |
+ // ChromeAppStartupParameters. |
+ MainController* controller = GetMainController(); |
+ |
+ // The array with the different states to tests (active, not active). |
+ NSArray* applicationStatesToTest = @[ @YES, @NO ]; |
+ |
+ // Mock of TabOpening, preventing the creation of a new tab. |
+ base::scoped_nsobject<MockTabOpener> tabOpener([[MockTabOpener alloc] init]); |
+ |
+ // The keys for this dictionary is the URL to call openURL:. The value |
+ // from the key is either YES or NO to indicate if this is a valid URL |
+ // or not. |
+ NSNumber* kIsValid = [NSNumber numberWithBool:YES]; |
+ NSNumber* kNotValid = [NSNumber numberWithBool:NO]; |
+ NSDictionary* urlsToTest = [NSDictionary |
+ dictionaryWithObjectsAndKeys: |
+ kNotValid, [NSNull null], |
+ // Tests for http, googlechrome, and chromium scheme URLs. |
+ kNotValid, @"", kIsValid, @"http://www.google.com/", kIsValid, |
+ @"https://www.google.com/settings/account/", kIsValid, |
+ @"googlechrome://www.google.com/", kIsValid, |
+ @"googlechromes://www.google.com/settings/account/", kIsValid, |
+ @"chromium://www.google.com/", kIsValid, |
+ @"chromiums://www.google.com/settings/account/", |
+ // Google search results page URLs. |
+ kIsValid, @"https://www.google.com/search?q=pony&" |
+ "sugexp=chrome,mod=7&sourceid=chrome&ie=UTF-8", |
+ kIsValid, @"googlechromes://www.google.com/search?q=pony&" |
+ "sugexp=chrome,mod=7&sourceid=chrome&ie=UTF-8", |
+ // Other protocols. |
+ kIsValid, @"chromium-x-callback://x-callback-url/open?url=https://" |
+ "www.google.com&x-success=http://success", |
+ kIsValid, @"file://localhost/path/to/file.pdf", |
+ // Invalid format input URL will be ignored. |
+ kNotValid, @"this.is.not.a.valid.url", |
+ // Valid format but invalid data. |
+ kIsValid, @"this://is/garbage/but/valid", nil]; |
+ NSArray* sourcesToTest = [NSArray |
+ arrayWithObjects:[NSNull null], @"", @"com.google.GoogleMobile", |
+ @"com.google.GooglePlus", @"com.google.SomeOtherProduct", |
+ @"com.apple.mobilesafari", |
+ @"com.othercompany.otherproduct", nil]; |
+ // See documentation for |annotation| property in |
+ // UIDocumentInteractionController Class Reference. The following values are |
+ // mostly to detect garbage-in situations and ensure that the app won't crash |
+ // or garbage out. |
+ NSArray* annotationsToTest = [NSArray |
+ arrayWithObjects:[NSNull null], |
+ [NSArray arrayWithObjects:@"foo", @"bar", nil], |
+ [NSDictionary dictionaryWithObject:@"bar" forKey:@"foo"], |
+ @"a string annotation object", nil]; |
+ for (id urlString in [urlsToTest allKeys]) { |
+ for (id source in sourcesToTest) { |
+ for (id annotation in annotationsToTest) { |
+ for (NSNumber* applicationActive in applicationStatesToTest) { |
+ BOOL applicationIsActive = [applicationActive boolValue]; |
+ |
+ controller.startupParameters = nil; |
+ [tabOpener resetURL]; |
+ NSURL* testUrl = urlString == [NSNull null] |
+ ? nil |
+ : [NSURL URLWithString:urlString]; |
+ BOOL isValid = [[urlsToTest objectForKey:urlString] boolValue]; |
+ base::scoped_nsobject<NSMutableDictionary> options( |
+ [[NSMutableDictionary alloc] init]); |
+ if (source != [NSNull null]) { |
+ [options setObject:source |
+ forKey:UIApplicationOpenURLOptionsSourceApplicationKey]; |
+ } |
+ if (annotation != [NSNull null]) { |
+ [options setObject:annotation |
+ forKey:UIApplicationOpenURLOptionsAnnotationKey]; |
+ } |
+ base::scoped_nsobject<ChromeAppStartupParameters> params( |
+ [ChromeAppStartupParameters |
+ newChromeAppStartupParametersWithURL:testUrl |
+ fromSourceApplication:nil]); |
+ |
+ // Action. |
+ BOOL result = [URLOpener openURL:testUrl |
+ applicationActive:applicationIsActive |
+ options:options |
+ tabOpener:tabOpener |
+ startupInformation:controller]; |
+ |
+ // Tests. |
+ EXPECT_EQ(isValid, result); |
+ if (!applicationIsActive) { |
+ if (result) |
+ EXPECT_EQ([params externalURL], |
+ controller.startupParameters.externalURL); |
+ else |
+ EXPECT_EQ(nil, controller.startupParameters); |
+ } else if (result) { |
+ EXPECT_EQ(nil, controller.startupParameters); |
+ EXPECT_EQ([params externalURL], [tabOpener url]); |
+ } |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+// Tests that -handleApplication set startup parameters as expected. |
+TEST_F(URLOpenerTest, VerifyLaunchOptions) { |
+ // Setup. |
+ NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"]; |
+ NSDictionary* launchOptions = @{ |
+ UIApplicationLaunchOptionsURLKey : url, |
+ UIApplicationLaunchOptionsSourceApplicationKey : @"com.apple.mobilesafari" |
+ }; |
+ |
+ id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)]; |
+ |
+ id startupInformationMock = |
+ [OCMockObject mockForProtocol:@protocol(StartupInformation)]; |
+ |
+ id appStateMock = [OCMockObject mockForClass:[AppState class]]; |
+ [[appStateMock expect] launchFromURLHandled:YES]; |
+ |
+ __block BOOL hasBeenCalled = NO; |
+ |
+ id implementation_block = ^BOOL( |
+ id self, NSURL* urlArg, BOOL applicationActive, NSDictionary* options, |
+ id<TabOpening> tabOpener, id<StartupInformation> startupInformation) { |
+ hasBeenCalled = YES; |
+ EXPECT_EQ([url absoluteString], [urlArg absoluteString]); |
+ EXPECT_EQ(@"com.apple.mobilesafari", |
+ options[UIApplicationOpenURLOptionsSourceApplicationKey]); |
+ EXPECT_EQ(startupInformationMock, startupInformation); |
+ EXPECT_EQ(tabOpenerMock, tabOpener); |
+ return YES; |
+ }; |
+ ScopedBlockSwizzler URL_opening_openURL_swizzler([URLOpener class], |
+ @selector(openURL: |
+ applicationActive: |
+ options: |
+ tabOpener: |
+ startupInformation:), |
+ implementation_block); |
+ |
+ // Action. |
+ [URLOpener handleLaunchOptions:launchOptions |
+ applicationActive:NO |
+ tabOpener:tabOpenerMock |
+ startupInformation:startupInformationMock |
+ appState:appStateMock]; |
+ |
+ // Test. |
+ EXPECT_TRUE(hasBeenCalled); |
+ EXPECT_OCMOCK_VERIFY(startupInformationMock); |
+} |
+ |
+// Tests that -handleApplication set startup parameters as expected with options |
+// as nil. |
+TEST_F(URLOpenerTest, VerifyLaunchOptionsNil) { |
+ // Creates a mock with no stub. This test will pass only if we don't use these |
+ // objects. |
+ id startupInformationMock = |
+ [OCMockObject mockForProtocol:@protocol(StartupInformation)]; |
+ id appStateMock = [OCMockObject mockForClass:[AppState class]]; |
+ |
+ // Action. |
+ [URLOpener handleLaunchOptions:nil |
+ applicationActive:YES |
+ tabOpener:nil |
+ startupInformation:startupInformationMock |
+ appState:appStateMock]; |
+} |
+ |
+// Tests that -handleApplication set startup parameters as expected with no |
+// source application. |
+TEST_F(URLOpenerTest, VerifyLaunchOptionsWithNoSourceApplication) { |
+ // Setup. |
+ NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"]; |
+ NSDictionary* launchOptions = @{ |
+ UIApplicationLaunchOptionsURLKey : url, |
+ }; |
+ |
+ // Creates a mock with no stub. This test will pass only if we don't use these |
+ // objects. |
+ id startupInformationMock = |
+ [OCMockObject mockForProtocol:@protocol(StartupInformation)]; |
+ id appStateMock = [OCMockObject mockForClass:[AppState class]]; |
+ |
+ // Action. |
+ [URLOpener handleLaunchOptions:launchOptions |
+ applicationActive:YES |
+ tabOpener:nil |
+ startupInformation:startupInformationMock |
+ appState:appStateMock]; |
+} |
+ |
+// Tests that -handleApplication set startup parameters as expected with no url. |
+TEST_F(URLOpenerTest, VerifyLaunchOptionsWithNoURL) { |
+ // Setup. |
+ NSDictionary* launchOptions = @{ |
+ UIApplicationLaunchOptionsSourceApplicationKey : @"com.apple.mobilesafari" |
+ }; |
+ |
+ // Creates a mock with no stub. This test will pass only if we don't use these |
+ // objects. |
+ id startupInformationMock = |
+ [OCMockObject mockForProtocol:@protocol(StartupInformation)]; |
+ id appStateMock = [OCMockObject mockForClass:[AppState class]]; |
+ |
+ // Action. |
+ [URLOpener handleLaunchOptions:launchOptions |
+ applicationActive:YES |
+ tabOpener:nil |
+ startupInformation:startupInformationMock |
+ appState:appStateMock]; |
+} |
+ |
+// Tests that -handleApplication set startup parameters as expected with a bad |
+// url. |
+TEST_F(URLOpenerTest, VerifyLaunchOptionsWithBadURL) { |
+ // Setup. |
+ NSURL* url = [NSURL URLWithString:@"chromium.www.google.com"]; |
+ NSDictionary* launchOptions = @{ |
+ UIApplicationLaunchOptionsURLKey : url, |
+ UIApplicationLaunchOptionsSourceApplicationKey : @"com.apple.mobilesafari" |
+ }; |
+ |
+ id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)]; |
+ |
+ id startupInformationMock = |
+ [OCMockObject mockForProtocol:@protocol(StartupInformation)]; |
+ |
+ id appStateMock = [OCMockObject mockForClass:[AppState class]]; |
+ [[appStateMock expect] launchFromURLHandled:YES]; |
+ |
+ __block BOOL hasBeenCalled = NO; |
+ |
+ id implementation_block = ^BOOL( |
+ id self, NSURL* urlArg, BOOL applicationActive, NSDictionary* options, |
+ id<TabOpening> tabOpener, id<StartupInformation> startupInformation) { |
+ hasBeenCalled = YES; |
+ EXPECT_EQ([url absoluteString], [urlArg absoluteString]); |
+ EXPECT_EQ(@"com.apple.mobilesafari", |
+ options[UIApplicationOpenURLOptionsSourceApplicationKey]); |
+ EXPECT_EQ(startupInformationMock, startupInformation); |
+ EXPECT_EQ(tabOpenerMock, tabOpener); |
+ return YES; |
+ }; |
+ ScopedBlockSwizzler URL_opening_openURL_swizzler([URLOpener class], |
+ @selector(openURL: |
+ applicationActive: |
+ options: |
+ tabOpener: |
+ startupInformation:), |
+ implementation_block); |
+ |
+ // Action. |
+ [URLOpener handleLaunchOptions:launchOptions |
+ applicationActive:NO |
+ tabOpener:tabOpenerMock |
+ startupInformation:startupInformationMock |
+ appState:appStateMock]; |
+ |
+ // Test. |
+ EXPECT_TRUE(hasBeenCalled); |
+ EXPECT_OCMOCK_VERIFY(startupInformationMock); |
+} |