blob: 444fc5b032b532968a1bc24deab490103cb707c0 [file] [log] [blame]
// Copyright 2017 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.
#import "chrome/browser/ui/cocoa/share_menu_controller.h"
#import "base/mac/scoped_nsobject.h"
#import "base/path_service.h"
#include "base/strings/sys_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "net/base/mac/url_conversions.h"
#include "testing/gtest_mac.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/events/test/cocoa_test_event_utils.h"
// Mock sharing service for sensing shared items.
@interface MockSharingService : NSSharingService
// Weak since both this object and the shared item
// should only live in the scope of the test.
@property(nonatomic, assign) id sharedItem;
@end
@implementation MockSharingService
// The real one is backed by SHKSharingService parameters which
// don't appear to be present when inheriting from vanilla
// |NSSharingService|.
@synthesize subject;
@synthesize sharedItem = _sharedItem;
- (void)performWithItems:(NSArray*)items {
[self setSharedItem:[items firstObject]];
}
@end
namespace {
base::scoped_nsobject<MockSharingService> MakeMockSharingService() {
return base::scoped_nsobject<MockSharingService>([[MockSharingService alloc]
initWithTitle:@"Mock service"
image:[NSImage imageNamed:NSImageNameAddTemplate]
alternateImage:nil
handler:^{
}]);
}
} // namespace
class ShareMenuControllerTest : public InProcessBrowserTest {
public:
ShareMenuControllerTest() {}
void SetUpOnMainThread() override {
base::FilePath test_data_dir;
base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
embedded_test_server()->ServeFilesFromDirectory(test_data_dir);
ASSERT_TRUE(embedded_test_server()->Start());
url_ = embedded_test_server()->GetURL("/title2.html");
ASSERT_TRUE(AddTabAtIndex(0, url_, ui::PAGE_TRANSITION_TYPED));
controller_.reset([[ShareMenuController alloc] init]);
}
protected:
// Create a menu item for |service| and trigger it using
// the target/action of real menu items created by
// |controller_|
void PerformShare(NSSharingService* service) {
base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Share"]);
[controller_ menuNeedsUpdate:menu];
base::scoped_nsobject<NSMenuItem> mock_menu_item([[NSMenuItem alloc]
initWithTitle:@"test"
action:nil
keyEquivalent:@""]);
[mock_menu_item setRepresentedObject:service];
NSMenuItem* first_menu_item = [menu itemAtIndex:0];
id target = [first_menu_item target];
SEL action = [first_menu_item action];
[target performSelector:action withObject:mock_menu_item.get()];
}
GURL url_;
base::scoped_nsobject<ShareMenuController> controller_;
};
IN_PROC_BROWSER_TEST_F(ShareMenuControllerTest, PopulatesMenu) {
base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Share"]);
NSArray* sharing_services_for_url = [NSSharingService
sharingServicesForItems:@[ [NSURL URLWithString:@"http://example.com"] ]];
EXPECT_GT([sharing_services_for_url count], 0U);
[controller_ menuNeedsUpdate:menu];
// -1 for reading list, +1 for "More..." if it's showing.
// This cancels out, so only decrement if the "More..." item
// isn't showing.
NSInteger expected_count = [sharing_services_for_url count];
EXPECT_EQ([menu numberOfItems], expected_count);
NSSharingService* reading_list_service = [NSSharingService
sharingServiceNamed:NSSharingServiceNameAddToSafariReadingList];
NSUInteger i = 0;
// Ensure there's a menu item for each service besides reading list.
for (NSSharingService* service in sharing_services_for_url) {
if ([service isEqual:reading_list_service])
continue;
NSMenuItem* menu_item = [menu itemAtIndex:i];
EXPECT_NSEQ([menu_item representedObject], service);
EXPECT_EQ([menu_item target], static_cast<id>(controller_));
++i;
}
// Ensure the menu is cleared between updates.
[controller_ menuNeedsUpdate:menu];
EXPECT_EQ([menu numberOfItems], expected_count);
}
IN_PROC_BROWSER_TEST_F(ShareMenuControllerTest, AddsMoreButton) {
base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Share"]);
[controller_ menuNeedsUpdate:menu];
NSInteger number_of_items = [menu numberOfItems];
EXPECT_GT(number_of_items, 0);
NSMenuItem* last_item = [menu itemAtIndex:number_of_items - 1];
EXPECT_NSEQ(last_item.title, l10n_util::GetNSString(IDS_SHARING_MORE_MAC));
}
IN_PROC_BROWSER_TEST_F(ShareMenuControllerTest, ActionPerformsShare) {
base::scoped_nsobject<MockSharingService> service = MakeMockSharingService();
EXPECT_FALSE([service sharedItem]);
PerformShare(service);
EXPECT_NSEQ([service sharedItem], net::NSURLWithGURL(url_));
// Title of chrome/test/data/title2.html
EXPECT_NSEQ([service subject], @"Title Of Awesomeness");
EXPECT_EQ([service delegate],
static_cast<id<NSSharingServiceDelegate>>(controller_));
}
IN_PROC_BROWSER_TEST_F(ShareMenuControllerTest, SharingDelegate) {
NSURL* url = [NSURL URLWithString:@"http://google.com"];
base::scoped_nsobject<NSSharingService> service([[NSSharingService alloc]
initWithTitle:@"Mock service"
image:[NSImage imageNamed:NSImageNameAddTemplate]
alternateImage:nil
handler:^{
// Verify inside the block since everything is cleared after the
// share.
// Extra service since the service param on the delegate
// methods is nonnull and circular references could get hairy.
base::scoped_nsobject<MockSharingService> mockService =
MakeMockSharingService();
NSWindow* browser_window =
browser()->window()->GetNativeWindow().GetNativeNSWindow();
EXPECT_NSNE([controller_ sharingService:mockService
sourceFrameOnScreenForShareItem:url],
NSZeroRect);
NSSharingContentScope scope = NSSharingContentScopeItem;
EXPECT_NSEQ([controller_ sharingService:mockService
sourceWindowForShareItems:@[ url ]
sharingContentScope:&scope],
browser_window);
EXPECT_EQ(scope, NSSharingContentScopeFull);
NSRect contentRect;
EXPECT_TRUE([controller_ sharingService:mockService
transitionImageForShareItem:url
contentRect:&contentRect]);
}]);
PerformShare(service);
}
IN_PROC_BROWSER_TEST_F(ShareMenuControllerTest, Histograms) {
base::HistogramTester tester;
const std::string histogram_name = "Mac.FileMenuNativeShare";
tester.ExpectTotalCount(histogram_name, 0);
base::scoped_nsobject<MockSharingService> service = MakeMockSharingService();
[controller_ sharingService:service didShareItems:@[]];
tester.ExpectBucketCount(histogram_name, true, 1);
tester.ExpectTotalCount(histogram_name, 1);
[controller_ sharingService:service didShareItems:@[]];
tester.ExpectBucketCount(histogram_name, true, 2);
tester.ExpectTotalCount(histogram_name, 2);
[controller_
sharingService:service
didFailToShareItems:@[]
error:[NSError errorWithDomain:@"" code:0 userInfo:nil]];
tester.ExpectTotalCount(histogram_name, 3);
tester.ExpectBucketCount(histogram_name, false, 1);
}
IN_PROC_BROWSER_TEST_F(ShareMenuControllerTest, MenuHasKeyEquivalent) {
// If this method isn't implemented, |menuNeedsUpdate:| is called any time
// *any* hotkey is used
ASSERT_TRUE([controller_ respondsToSelector:@selector
(menuHasKeyEquivalent:forEvent:target:action:)]);
// Ensure that calling |menuHasKeyEquivalent:...| the first time populates the
// menu.
base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Share"]);
EXPECT_EQ([menu numberOfItems], 0);
NSEvent* event = cocoa_test_event_utils::KeyEventWithKeyCode(
'i', 'i', NSEventTypeKeyDown,
NSEventModifierFlagCommand | NSEventModifierFlagShift);
id ignored_target;
SEL ignored_action;
EXPECT_FALSE([controller_ menuHasKeyEquivalent:menu
forEvent:event
target:&ignored_target
action:&ignored_action]);
EXPECT_GT([menu numberOfItems], 0);
NSMenuItem* item = [menu itemAtIndex:0];
// |menuHasKeyEquivalent:....| shouldn't populate the menu after the first
// time.
[controller_ menuHasKeyEquivalent:menu
forEvent:event
target:&ignored_target
action:&ignored_action];
EXPECT_EQ(item, [menu itemAtIndex:0]); // Pointer equality intended.
}