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.
5 #import "chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h"
7 #include "base/command_line.h"
8 #import "base/mac/foundation_util.h"
9 #include "base/mac/scoped_nsobject.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/prefs/pref_service_syncable.h"
14 #include "chrome/browser/profiles/avatar_menu.h"
15 #include "chrome/browser/profiles/profile_info_cache.h"
16 #include "chrome/browser/signin/account_tracker_service_factory.h"
17 #include "chrome/browser/signin/fake_account_tracker_service.h"
18 #include "chrome/browser/signin/fake_profile_oauth2_token_service.h"
19 #include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h"
20 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
21 #include "chrome/browser/signin/signin_header_helper.h"
22 #include "chrome/browser/signin/signin_manager_factory.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "components/signin/core/browser/profile_oauth2_token_service.h"
27 #include "components/signin/core/browser/signin_manager.h"
28 #include "components/signin/core/common/profile_management_switches.h"
30 const std::string kEmail = "user@gmail.com";
31 const std::string kSecondaryEmail = "user2@gmail.com";
32 const std::string kLoginToken = "oauth2_login_token";
34 class ProfileChooserControllerTest : public CocoaProfileTest {
36 ProfileChooserControllerTest() {
39 virtual void SetUp() OVERRIDE {
40 CocoaProfileTest::SetUp();
41 ASSERT_TRUE(browser()->profile());
43 AccountTrackerServiceFactory::GetInstance()->SetTestingFactory(
44 browser()->profile(), FakeAccountTrackerService::Build);
46 TestingProfile::TestingFactories factories;
48 std::make_pair(ProfileOAuth2TokenServiceFactory::GetInstance(),
49 BuildFakeProfileOAuth2TokenService));
51 std::make_pair(AccountTrackerServiceFactory::GetInstance(),
52 FakeAccountTrackerService::Build));
53 testing_profile_manager()->
54 CreateTestingProfile("test1", scoped_ptr<PrefServiceSyncable>(),
55 base::ASCIIToUTF16("Test 1"), 0, std::string(),
57 testing_profile_manager()->
58 CreateTestingProfile("test2", scoped_ptr<PrefServiceSyncable>(),
59 base::ASCIIToUTF16("Test 2"), 1, std::string(),
60 TestingProfile::TestingFactories());
62 menu_ = new AvatarMenu(testing_profile_manager()->profile_info_cache(),
66 // There should be the default profile + two profiles we created.
67 EXPECT_EQ(3U, menu_->GetNumberOfItems());
70 virtual void TearDown() OVERRIDE {
73 CocoaProfileTest::TearDown();
76 void StartProfileChooserController() {
77 NSRect frame = [test_window() frame];
78 NSPoint point = NSMakePoint(NSMidX(frame), NSMidY(frame));
79 controller_.reset([[ProfileChooserController alloc]
80 initWithBrowser:browser()
82 viewMode:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER
83 tutorialMode:profiles::TUTORIAL_MODE_NONE
84 serviceType:signin::GAIA_SERVICE_TYPE_NONE]);
85 [controller_ showWindow:nil];
88 void EnableFastUserSwitching() {
89 CommandLine::ForCurrentProcess()->AppendSwitch(
90 switches::kFastUserSwitching);
93 ProfileChooserController* controller() { return controller_; }
94 AvatarMenu* menu() { return menu_; }
97 base::scoped_nsobject<ProfileChooserController> controller_;
99 // Weak; owned by |controller_|.
102 DISALLOW_COPY_AND_ASSIGN(ProfileChooserControllerTest);
105 TEST_F(ProfileChooserControllerTest, InitialLayoutWithNewMenu) {
106 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
107 StartProfileChooserController();
109 NSArray* subviews = [[[controller() window] contentView] subviews];
110 ASSERT_EQ(2U, [subviews count]);
111 subviews = [[subviews objectAtIndex:0] subviews];
113 // Three profiles means we should have one active card, one separator and
114 // one option buttons view. We also have an update promo for the new avatar
116 // TODO(noms): Enforcing 4U fails on the waterfall debug bots, but it's not
117 // reproducible anywhere else.
118 ASSERT_GE([subviews count], 3U);
120 // There should be two buttons and a separator in the option buttons view.
121 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
122 ASSERT_EQ(3U, [buttonSubviews count]);
124 // There should be an incognito button.
125 NSButton* incognitoButton =
126 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
127 EXPECT_EQ(@selector(goIncognito:), [incognitoButton action]);
128 EXPECT_EQ(controller(), [incognitoButton target]);
130 // There should be a separator.
131 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
133 // There should be a user switcher button.
134 NSButton* userSwitcherButton =
135 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:2]);
136 EXPECT_EQ(@selector(showUserManager:), [userSwitcherButton action]);
137 EXPECT_EQ(controller(), [userSwitcherButton target]);
139 // There should be a separator.
140 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
142 // There should be the profile avatar, name and links container in the active
143 // card view. The links displayed in the container are checked separately.
144 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
145 ASSERT_EQ(3U, [activeCardSubviews count]);
148 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
149 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSButton class]]);
152 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
153 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
154 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
155 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
157 // Profile links. This is a local profile, so there should be a signin button
158 // and a signin promo.
159 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
160 ASSERT_EQ(2U, [linksSubviews count]);
161 NSButton* link = base::mac::ObjCCast<NSButton>(
162 [linksSubviews objectAtIndex:0]);
163 EXPECT_EQ(@selector(showInlineSigninPage:), [link action]);
164 EXPECT_EQ(controller(), [link target]);
166 NSTextField* promo = base::mac::ObjCCast<NSTextField>(
167 [linksSubviews objectAtIndex:1]);
168 EXPECT_GT([[promo stringValue] length], 0U);
171 TEST_F(ProfileChooserControllerTest, InitialLayoutWithFastUserSwitcher) {
172 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
173 EnableFastUserSwitching();
174 StartProfileChooserController();
176 NSArray* subviews = [[[controller() window] contentView] subviews];
177 ASSERT_EQ(2U, [subviews count]);
178 subviews = [[subviews objectAtIndex:0] subviews];
180 // Three profiles means we should have one active card and a
181 // fast user switcher which has two "other" profiles and 2 separators, and
182 // an option buttons view with its separator. We also have a promo for
183 // the new avatar menu.
184 // TODO(noms): Enforcing 8U fails on the waterfall debug bots, but it's not
185 // reproducible anywhere else.
186 ASSERT_GE([subviews count], 7U);
188 // There should be two buttons and a separator in the option buttons view.
189 // These buttons are tested in InitialLayoutWithNewMenu.
190 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
191 ASSERT_EQ(3U, [buttonSubviews count]);
193 // There should be a separator.
194 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
196 // There should be two "other profiles" items. The items are drawn from the
197 // bottom up, so in the opposite order of those in the AvatarMenu.
198 int profileIndex = 1;
199 for (int i = 5; i >= 2; i -= 2) {
200 // Each profile button has a separator.
201 EXPECT_TRUE([[subviews objectAtIndex:i] isKindOfClass:[NSBox class]]);
203 NSButton* button = base::mac::ObjCCast<NSButton>(
204 [subviews objectAtIndex:i-1]);
205 EXPECT_EQ(menu()->GetItemAt(profileIndex).name,
206 base::SysNSStringToUTF16([button title]));
207 EXPECT_EQ(profileIndex, [button tag]);
208 EXPECT_EQ(@selector(switchToProfile:), [button action]);
209 EXPECT_EQ(controller(), [button target]);
213 // There should be the profile avatar, name and links container in the active
214 // card view. The links displayed in the container are checked separately.
215 NSArray* activeCardSubviews = [[subviews objectAtIndex:6] subviews];
216 ASSERT_EQ(3U, [activeCardSubviews count]);
219 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
220 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSButton class]]);
223 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
224 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
225 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
226 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
228 // Profile links. This is a local profile, so there should be a signin button
229 // and a signin promo. These are also tested in InitialLayoutWithNewMenu.
230 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
231 EXPECT_EQ(2U, [linksSubviews count]);
234 TEST_F(ProfileChooserControllerTest, OtherProfilesSortedAlphabetically) {
235 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
236 EnableFastUserSwitching();
238 // Add two extra profiles, to make sure sorting is alphabetical and not
239 // by order of creation.
240 testing_profile_manager()->
241 CreateTestingProfile("test3", scoped_ptr<PrefServiceSyncable>(),
242 base::ASCIIToUTF16("New Profile"), 1, std::string(),
243 TestingProfile::TestingFactories());
244 testing_profile_manager()->
245 CreateTestingProfile("test4", scoped_ptr<PrefServiceSyncable>(),
246 base::ASCIIToUTF16("Another Test"), 1, std::string(),
247 TestingProfile::TestingFactories());
248 StartProfileChooserController();
250 NSArray* subviews = [[[controller() window] contentView] subviews];
251 ASSERT_EQ(2U, [subviews count]);
252 subviews = [[subviews objectAtIndex:0] subviews];
253 NSString* sortedNames[] = { @"Another Test",
257 // There are four "other" profiles, each with a button and a separator, an
258 // active profile card, and an option buttons view with a separator. We
259 // also have an update promo for the new avatar menu.
260 // TODO(noms): Enforcing 12U fails on the waterfall debug bots, but it's not
261 // reproducible anywhere else.
262 ASSERT_GE([subviews count], 11U);
263 // There should be four "other profiles" items, sorted alphabetically. The
264 // "other profiles" start at index 2 (after the option buttons view and its
265 // separator), and each have a separator. We need to iterate through the
266 // profiles in the order displayed in the bubble, which is opposite from the
268 int sortedNameIndex = 0;
269 for (int i = 9; i >= 2; i -= 2) {
270 // The item at index i is the separator.
271 NSButton* button = base::mac::ObjCCast<NSButton>(
272 [subviews objectAtIndex:i-1]);
274 [[button title] isEqualToString:sortedNames[sortedNameIndex++]]);
278 TEST_F(ProfileChooserControllerTest,
279 LocalProfileActiveCardLinksWithNewMenu) {
280 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
281 StartProfileChooserController();
282 NSArray* subviews = [[[controller() window] contentView] subviews];
283 ASSERT_EQ(2U, [subviews count]);
284 subviews = [[subviews objectAtIndex:0] subviews];
285 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
286 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
288 ASSERT_EQ(2U, [activeCardLinks count]);
290 // There should be a sign in button.
291 NSButton* link = base::mac::ObjCCast<NSButton>(
292 [activeCardLinks objectAtIndex:0]);
293 EXPECT_EQ(@selector(showInlineSigninPage:), [link action]);
294 EXPECT_EQ(controller(), [link target]);
296 // Local profiles have a signin promo.
297 NSTextField* promo = base::mac::ObjCCast<NSTextField>(
298 [activeCardLinks objectAtIndex:1]);
299 EXPECT_GT([[promo stringValue] length], 0U);
302 TEST_F(ProfileChooserControllerTest,
303 SignedInProfileActiveCardLinksWithAccountConsistency) {
304 switches::EnableAccountConsistencyForTesting(
305 CommandLine::ForCurrentProcess());
306 // Sign in the first profile.
307 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
308 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
310 StartProfileChooserController();
311 NSArray* subviews = [[[controller() window] contentView] subviews];
312 ASSERT_EQ(2U, [subviews count]);
313 subviews = [[subviews objectAtIndex:0] subviews];
314 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
315 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
317 // There is one link: manage accounts.
318 ASSERT_EQ(1U, [activeCardLinks count]);
319 NSButton* manageAccountsLink =
320 base::mac::ObjCCast<NSButton>([activeCardLinks objectAtIndex:0]);
321 EXPECT_EQ(@selector(showAccountManagement:), [manageAccountsLink action]);
322 EXPECT_EQ(controller(), [manageAccountsLink target]);
325 TEST_F(ProfileChooserControllerTest,
326 SignedInProfileActiveCardLinksWithNewMenu) {
327 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
328 // Sign in the first profile.
329 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
330 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
332 StartProfileChooserController();
333 NSArray* subviews = [[[controller() window] contentView] subviews];
334 ASSERT_EQ(2U, [subviews count]);
335 subviews = [[subviews objectAtIndex:0] subviews];
336 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
337 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
339 // There is one disabled button with the user's email.
340 ASSERT_EQ(1U, [activeCardLinks count]);
341 NSButton* emailButton =
342 base::mac::ObjCCast<NSButton>([activeCardLinks objectAtIndex:0]);
343 EXPECT_EQ(kEmail, base::SysNSStringToUTF8([emailButton title]));
344 EXPECT_EQ(nil, [emailButton action]);
345 EXPECT_FALSE([emailButton isEnabled]);
348 TEST_F(ProfileChooserControllerTest, AccountManagementLayout) {
349 switches::EnableAccountConsistencyForTesting(
350 CommandLine::ForCurrentProcess());
351 // Sign in the first profile.
352 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
353 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
355 // Set up the signin manager and the OAuth2Tokens.
356 Profile* profile = browser()->profile();
357 SigninManagerFactory::GetForProfile(profile)->
358 SetAuthenticatedUsername(kEmail);
359 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
360 UpdateCredentials(kEmail, kLoginToken);
361 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
362 UpdateCredentials(kSecondaryEmail, kLoginToken);
364 StartProfileChooserController();
365 [controller() initMenuContentsWithView:
366 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
368 NSArray* subviews = [[[controller() window] contentView] subviews];
369 ASSERT_EQ(2U, [subviews count]);
370 subviews = [[subviews objectAtIndex:0] subviews];
372 // There should be one active card, one accounts container, two separators
373 // and one option buttons view.
374 ASSERT_EQ(5U, [subviews count]);
376 // There should be three buttons and two separators in the option
378 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
379 ASSERT_EQ(5U, [buttonSubviews count]);
381 // There should be a lock button.
382 NSButton* lockButton =
383 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
384 EXPECT_EQ(@selector(lockProfile:), [lockButton action]);
385 EXPECT_EQ(controller(), [lockButton target]);
387 // There should be a separator.
388 EXPECT_TRUE([[buttonSubviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
390 // There should be an incognito button.
391 NSButton* incognitoButton =
392 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:2]);
393 EXPECT_EQ(@selector(goIncognito:), [incognitoButton action]);
394 EXPECT_EQ(controller(), [incognitoButton target]);
396 // There should be a separator.
397 EXPECT_TRUE([[subviews objectAtIndex:3] isKindOfClass:[NSBox class]]);
399 // There should be a user switcher button.
400 NSButton* userSwitcherButton =
401 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:4]);
402 EXPECT_EQ(@selector(showUserManager:), [userSwitcherButton action]);
403 EXPECT_EQ(controller(), [userSwitcherButton target]);
405 // In the accounts view, there should be the account list container
406 // accounts and one "add accounts" button.
407 NSArray* accountsSubviews = [[subviews objectAtIndex:2] subviews];
408 ASSERT_EQ(2U, [accountsSubviews count]);
410 NSButton* addAccountsButton =
411 base::mac::ObjCCast<NSButton>([accountsSubviews objectAtIndex:0]);
412 EXPECT_EQ(@selector(addAccount:), [addAccountsButton action]);
413 EXPECT_EQ(controller(), [addAccountsButton target]);
415 // There should be two accounts in the account list container.
416 NSArray* accountsListSubviews = [[accountsSubviews objectAtIndex:1] subviews];
417 ASSERT_EQ(2U, [accountsListSubviews count]);
419 NSButton* genericAccount =
420 base::mac::ObjCCast<NSButton>([accountsListSubviews objectAtIndex:0]);
421 NSButton* genericAccountDelete = base::mac::ObjCCast<NSButton>(
422 [[genericAccount subviews] objectAtIndex:0]);
423 EXPECT_EQ(@selector(showAccountRemovalView:), [genericAccountDelete action]);
424 EXPECT_EQ(controller(), [genericAccountDelete target]);
425 EXPECT_NE(-1, [genericAccountDelete tag]);
427 // Primary accounts are always last.
428 NSButton* primaryAccount =
429 base::mac::ObjCCast<NSButton>([accountsListSubviews objectAtIndex:1]);
430 NSButton* primaryAccountDelete = base::mac::ObjCCast<NSButton>(
431 [[primaryAccount subviews] objectAtIndex:0]);
432 EXPECT_EQ(@selector(showAccountRemovalView:), [primaryAccountDelete action]);
433 EXPECT_EQ(controller(), [primaryAccountDelete target]);
434 EXPECT_EQ(-1, [primaryAccountDelete tag]);
436 // There should be another separator.
437 EXPECT_TRUE([[subviews objectAtIndex:3] isKindOfClass:[NSBox class]]);
439 // There should be the profile avatar, name and a "hide accounts" link
440 // container in the active card view.
441 NSArray* activeCardSubviews = [[subviews objectAtIndex:4] subviews];
442 ASSERT_EQ(3U, [activeCardSubviews count]);
445 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
446 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSButton class]]);
449 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
450 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
451 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
452 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
454 // Profile links. This is a local profile, so there should be a signin button.
455 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
456 ASSERT_EQ(1U, [linksSubviews count]);
457 NSButton* link = base::mac::ObjCCast<NSButton>(
458 [linksSubviews objectAtIndex:0]);
459 EXPECT_EQ(@selector(hideAccountManagement:), [link action]);
460 EXPECT_EQ(controller(), [link target]);