1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "chrome/browser/ui/cocoa/profiles/profile_menu_controller.h" 6 7#include "base/mac/scoped_nsobject.h" 8#include "base/threading/thread_restrictions.h" 9#include "chrome/browser/profiles/profile_manager.h" 10#include "chrome/browser/ui/cocoa/test/cocoa_test_helper.h" 11#include "chrome/browser/ui/cocoa/test/run_loop_testing.h" 12#include "chrome/common/pref_names.h" 13#include "chrome/grit/generated_resources.h" 14#include "chrome/test/base/browser_with_test_window_test.h" 15#include "chrome/test/base/test_browser_window.h" 16#include "chrome/test/base/testing_browser_process.h" 17#include "chrome/test/base/testing_profile.h" 18#include "chrome/test/base/testing_profile_manager.h" 19#include "testing/gtest_mac.h" 20#include "ui/base/l10n/l10n_util_mac.h" 21 22class ProfileMenuControllerTest : public BrowserWithTestWindowTest { 23 public: 24 ProfileMenuControllerTest() { RebuildController(); } 25 26 void SetUp() override { 27 CocoaTest::BootstrapCocoa(); 28 BrowserWithTestWindowTest::SetUp(); 29 30 // Spin the runloop so |-initializeMenu| gets called. 31 chrome::testing::NSRunLoopRunAllPending(); 32 } 33 34 void RebuildController() { 35 item_.reset([[NSMenuItem alloc] initWithTitle:@"Users" 36 action:nil 37 keyEquivalent:@""]); 38 controller_.reset( 39 [[ProfileMenuController alloc] initWithMainMenuItem:item_]); 40 } 41 42 void TestBottomItems() { 43 NSMenu* menu = [controller() menu]; 44 NSInteger count = [menu numberOfItems]; 45 46 ASSERT_GE(count, 4); 47 48 NSMenuItem* item = [menu itemAtIndex:count - 4]; 49 EXPECT_TRUE([item isSeparatorItem]); 50 51 item = [menu itemAtIndex:count - 3]; 52 EXPECT_EQ(@selector(editProfile:), [item action]); 53 54 item = [menu itemAtIndex:count - 2]; 55 EXPECT_TRUE([item isSeparatorItem]); 56 57 item = [menu itemAtIndex:count - 1]; 58 EXPECT_EQ(@selector(newProfile:), [item action]); 59 } 60 61 void VerifyProfileNamedIsActive(NSString* title, int line) { 62 for (NSMenuItem* item in [[controller() menu] itemArray]) { 63 if ([[item title] isEqualToString:title]) { 64 EXPECT_EQ(NSOnState, [item state]) << [[item title] UTF8String] 65 << " (from line " << line << ")"; 66 } else { 67 EXPECT_EQ(NSOffState, [item state]) << [[item title] UTF8String] 68 << " (from line " << line << ")"; 69 } 70 } 71 } 72 73 ProfileMenuController* controller() { return controller_.get(); } 74 75 NSMenuItem* menu_item() { return item_.get(); } 76 77 private: 78 base::scoped_nsobject<NSMenuItem> item_; 79 base::scoped_nsobject<ProfileMenuController> controller_; 80}; 81 82TEST_F(ProfileMenuControllerTest, InitializeMenu) { 83 NSMenu* menu = [controller() menu]; 84 // Profile, <sep>, Edit, <sep>, New. 85 ASSERT_EQ(5, [menu numberOfItems]); 86 87 TestBottomItems(); 88 89 EXPECT_FALSE([menu_item() isHidden]); 90} 91 92TEST_F(ProfileMenuControllerTest, CreateItemWithTitle) { 93 NSMenuItem* item = 94 [controller() createItemWithTitle:@"Title" 95 action:@selector(someSelector:)]; 96 EXPECT_NSEQ(@"Title", [item title]); 97 EXPECT_EQ(controller(), [item target]); 98 EXPECT_EQ(@selector(someSelector:), [item action]); 99 EXPECT_NSEQ(@"", [item keyEquivalent]); 100} 101 102TEST_F(ProfileMenuControllerTest, RebuildMenu) { 103 NSMenu* menu = [controller() menu]; 104 EXPECT_EQ(5, [menu numberOfItems]); 105 106 EXPECT_FALSE([menu_item() isHidden]); 107 108 // Create some more profiles on the manager. 109 TestingProfileManager* manager = profile_manager(); 110 manager->CreateTestingProfile("Profile 2"); 111 manager->CreateTestingProfile("Profile 3"); 112 113 // Verify that the menu got rebuilt. 114 ASSERT_EQ(7, [menu numberOfItems]); 115 116 NSMenuItem* item = [menu itemAtIndex:0]; 117 EXPECT_EQ(@selector(switchToProfileFromMenu:), [item action]); 118 119 item = [menu itemAtIndex:1]; 120 EXPECT_EQ(@selector(switchToProfileFromMenu:), [item action]); 121 122 item = [menu itemAtIndex:2]; 123 EXPECT_EQ(@selector(switchToProfileFromMenu:), [item action]); 124 125 TestBottomItems(); 126 127 EXPECT_FALSE([menu_item() isHidden]); 128} 129 130TEST_F(ProfileMenuControllerTest, InsertItems) { 131 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]); 132 ASSERT_EQ(0, [menu numberOfItems]); 133 134 // Even with one profile items can still be inserted. 135 BOOL result = [controller() insertItemsIntoMenu:menu 136 atOffset:0 137 fromDock:NO]; 138 EXPECT_TRUE(result); 139 EXPECT_EQ(1, [menu numberOfItems]); 140 [menu removeAllItems]; 141 142 // Same for use in building the dock menu. 143 result = [controller() insertItemsIntoMenu:menu 144 atOffset:0 145 fromDock:YES]; 146 EXPECT_FALSE(result); 147 EXPECT_EQ(0, [menu numberOfItems]); 148 [menu removeAllItems]; 149 150 // Create one more profile on the manager. 151 TestingProfileManager* manager = profile_manager(); 152 manager->CreateTestingProfile("Profile 2"); 153 154 // With more than one profile, insertItems should return YES. 155 result = [controller() insertItemsIntoMenu:menu 156 atOffset:0 157 fromDock:NO]; 158 EXPECT_TRUE(result); 159 ASSERT_EQ(2, [menu numberOfItems]); 160 161 NSMenuItem* item = [menu itemAtIndex:0]; 162 EXPECT_EQ(@selector(switchToProfileFromMenu:), [item action]); 163 164 item = [menu itemAtIndex:1]; 165 EXPECT_EQ(@selector(switchToProfileFromMenu:), [item action]); 166 [menu removeAllItems]; 167 168 // And for the dock, the selector should be different and there should be a 169 // header item. 170 result = [controller() insertItemsIntoMenu:menu 171 atOffset:0 172 fromDock:YES]; 173 EXPECT_TRUE(result); 174 ASSERT_EQ(3, [menu numberOfItems]); 175 176 // First item is a label item. 177 item = [menu itemAtIndex:0]; 178 EXPECT_FALSE([item isEnabled]); 179 180 item = [menu itemAtIndex:1]; 181 EXPECT_EQ(@selector(switchToProfileFromDock:), [item action]); 182 183 item = [menu itemAtIndex:2]; 184 EXPECT_EQ(@selector(switchToProfileFromDock:), [item action]); 185} 186 187TEST_F(ProfileMenuControllerTest, InitialActiveBrowser) { 188 [controller() activeBrowserChangedTo:NULL]; 189 VerifyProfileNamedIsActive(l10n_util::GetNSString(IDS_DEFAULT_PROFILE_NAME), 190 __LINE__); 191} 192 193// Note: BrowserList::SetLastActive() is typically called as part of 194// BrowserWindow::Show() and when a Browser becomes active. We don't need a full 195// BrowserWindow, so it is called manually. 196TEST_F(ProfileMenuControllerTest, SetActiveAndRemove) { 197 NSMenu* menu = [controller() menu]; 198 TestingProfileManager* manager = profile_manager(); 199 TestingProfile* profile2 = manager->CreateTestingProfile("Profile 2"); 200 TestingProfile* profile3 = manager->CreateTestingProfile("Profile 3"); 201 ASSERT_EQ(7, [menu numberOfItems]); 202 203 // Create a browser and "show" it. 204 Browser::CreateParams profile2_params(profile2, true); 205 std::unique_ptr<Browser> p2_browser( 206 CreateBrowserWithTestWindowForParams(profile2_params)); 207 [controller() activeBrowserChangedTo:p2_browser.get()]; 208 VerifyProfileNamedIsActive(@"Profile 2", __LINE__); 209 210 // Close the browser and make sure it's still active. 211 p2_browser.reset(); 212 [controller() activeBrowserChangedTo:nil]; 213 VerifyProfileNamedIsActive(@"Profile 2", __LINE__); 214 215 // Open a new browser and make sure it takes effect. 216 Browser::CreateParams profile3_params(profile3, true); 217 std::unique_ptr<Browser> p3_browser( 218 CreateBrowserWithTestWindowForParams(profile3_params)); 219 [controller() activeBrowserChangedTo:p3_browser.get()]; 220 VerifyProfileNamedIsActive(@"Profile 3", __LINE__); 221 222 p3_browser.reset(); 223 [controller() activeBrowserChangedTo:nil]; 224 VerifyProfileNamedIsActive(@"Profile 3", __LINE__); 225} 226 227TEST_F(ProfileMenuControllerTest, DeleteActiveProfile) { 228 TestingProfileManager* manager = profile_manager(); 229 230 manager->CreateTestingProfile("Profile 2"); 231 TestingProfile* profile3 = manager->CreateTestingProfile("Profile 3"); 232 ASSERT_EQ(3U, manager->profile_manager()->GetNumberOfProfiles()); 233 234 const base::FilePath profile3_path = profile3->GetPath(); 235 manager->DeleteTestingProfile("Profile 3"); 236 237 // Simulate an unloaded profile by setting the "last used" local state pref 238 // the profile that was just deleted. 239 PrefService* local_state = g_browser_process->local_state(); 240 local_state->SetString(prefs::kProfileLastUsed, 241 profile3_path.BaseName().MaybeAsASCII()); 242 243 // Simulate the active browser changing to NULL and ensure a profile doesn't 244 // get created by disallowing IO operations temporarily. 245 const bool io_was_allowed = base::ThreadRestrictions::SetIOAllowed(false); 246 [controller() activeBrowserChangedTo:NULL]; 247 base::ThreadRestrictions::SetIOAllowed(io_was_allowed); 248} 249 250TEST_F(ProfileMenuControllerTest, AddProfileDisabled) { 251 PrefService* local_state = g_browser_process->local_state(); 252 local_state->SetBoolean(prefs::kBrowserAddPersonEnabled, false); 253 254 RebuildController(); 255 // Spin the runloop so |-initializeMenu| gets called. 256 chrome::testing::NSRunLoopRunAllPending(); 257 258 NSMenu* menu = [controller() menu]; 259 NSInteger count = [menu numberOfItems]; 260 261 ASSERT_GE(count, 2); 262 263 NSMenuItem* item = [menu itemAtIndex:count - 2]; 264 EXPECT_TRUE([item isSeparatorItem]); 265 266 item = [menu itemAtIndex:count - 1]; 267 EXPECT_EQ(@selector(editProfile:), [item action]); 268} 269