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