| 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);
|
| +}
|
|
|