| // 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. |
| } |