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/fake_profile_oauth2_token_service.h"
17 #include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h"
18 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
19 #include "chrome/browser/signin/signin_header_helper.h"
20 #include "chrome/browser/signin/signin_manager_factory.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "components/signin/core/browser/profile_oauth2_token_service.h"
25 #include "components/signin/core/browser/signin_manager.h"
26 #include "components/signin/core/common/profile_management_switches.h"
28 const std::string kEmail = "user@gmail.com";
29 const std::string kSecondaryEmail = "user2@gmail.com";
30 const std::string kLoginToken = "oauth2_login_token";
32 class ProfileChooserControllerTest : public CocoaProfileTest {
34 ProfileChooserControllerTest() {
37 virtual void SetUp() OVERRIDE {
38 CocoaProfileTest::SetUp();
39 ASSERT_TRUE(browser()->profile());
41 TestingProfile::TestingFactories factories;
43 std::make_pair(ProfileOAuth2TokenServiceFactory::GetInstance(),
44 BuildFakeProfileOAuth2TokenService));
45 testing_profile_manager()->
46 CreateTestingProfile("test1", scoped_ptr<PrefServiceSyncable>(),
47 base::ASCIIToUTF16("Test 1"), 0, std::string(),
49 testing_profile_manager()->
50 CreateTestingProfile("test2", scoped_ptr<PrefServiceSyncable>(),
51 base::ASCIIToUTF16("Test 2"), 1, std::string(),
52 TestingProfile::TestingFactories());
54 menu_ = new AvatarMenu(testing_profile_manager()->profile_info_cache(),
58 // There should be the default profile + two profiles we created.
59 EXPECT_EQ(3U, menu_->GetNumberOfItems());
62 virtual void TearDown() OVERRIDE {
65 CocoaProfileTest::TearDown();
68 void StartProfileChooserController() {
69 NSRect frame = [test_window() frame];
70 NSPoint point = NSMakePoint(NSMidX(frame), NSMidY(frame));
71 controller_.reset([[ProfileChooserController alloc]
72 initWithBrowser:browser()
74 viewMode:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER
75 tutorialMode:profiles::TUTORIAL_MODE_NONE
76 serviceType:signin::GAIA_SERVICE_TYPE_NONE]);
77 [controller_ showWindow:nil];
80 void EnableFastUserSwitching() {
81 CommandLine::ForCurrentProcess()->AppendSwitch(
82 switches::kFastUserSwitching);
85 ProfileChooserController* controller() { return controller_; }
86 AvatarMenu* menu() { return menu_; }
89 base::scoped_nsobject<ProfileChooserController> controller_;
91 // Weak; owned by |controller_|.
94 DISALLOW_COPY_AND_ASSIGN(ProfileChooserControllerTest);
97 TEST_F(ProfileChooserControllerTest, InitialLayoutWithNewMenu) {
98 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
99 StartProfileChooserController();
101 NSArray* subviews = [[[controller() window] contentView] subviews];
102 ASSERT_EQ(1U, [subviews count]);
103 subviews = [[subviews objectAtIndex:0] subviews];
105 // Three profiles means we should have one active card, one separator and
106 // one option buttons view. We also have an update promo for the new avatar
108 // TODO(noms): Enforcing 4U fails on the waterfall debug bots, but it's not
109 // reproducible anywhere else.
110 ASSERT_GE([subviews count], 3U);
112 // There should be two buttons and a separator in the option buttons view.
113 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
114 ASSERT_EQ(3U, [buttonSubviews count]);
116 // There should be an incognito button.
117 NSButton* incognitoButton =
118 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
119 EXPECT_EQ(@selector(goIncognito:), [incognitoButton action]);
120 EXPECT_EQ(controller(), [incognitoButton target]);
122 // There should be a separator.
123 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
125 // There should be a user switcher button.
126 NSButton* userSwitcherButton =
127 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:2]);
128 EXPECT_EQ(@selector(showUserManager:), [userSwitcherButton action]);
129 EXPECT_EQ(controller(), [userSwitcherButton target]);
131 // There should be a separator.
132 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
134 // There should be the profile avatar, name and links container in the active
135 // card view. The links displayed in the container are checked separately.
136 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
137 ASSERT_EQ(3U, [activeCardSubviews count]);
140 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
141 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSImageView class]]);
144 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
145 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
146 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
147 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
149 // Profile links. This is a local profile, so there should be a signin button
150 // and a signin promo.
151 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
152 ASSERT_EQ(2U, [linksSubviews count]);
153 NSButton* link = base::mac::ObjCCast<NSButton>(
154 [linksSubviews objectAtIndex:0]);
155 EXPECT_EQ(@selector(showInlineSigninPage:), [link action]);
156 EXPECT_EQ(controller(), [link target]);
158 NSTextField* promo = base::mac::ObjCCast<NSTextField>(
159 [linksSubviews objectAtIndex:1]);
160 EXPECT_GT([[promo stringValue] length], 0U);
163 TEST_F(ProfileChooserControllerTest, InitialLayoutWithFastUserSwitcher) {
164 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
165 EnableFastUserSwitching();
166 StartProfileChooserController();
168 NSArray* subviews = [[[controller() window] contentView] subviews];
169 ASSERT_EQ(1U, [subviews count]);
170 subviews = [[subviews objectAtIndex:0] subviews];
172 // Three profiles means we should have one active card and a
173 // fast user switcher which has two "other" profiles and 2 separators, and
174 // an option buttons view with its separator. We also have a promo for
175 // the new avatar menu.
176 // TODO(noms): Enforcing 8U fails on the waterfall debug bots, but it's not
177 // reproducible anywhere else.
178 ASSERT_GE([subviews count], 7U);
180 // There should be two buttons and a separator in the option buttons view.
181 // These buttons are tested in InitialLayoutWithNewMenu.
182 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
183 ASSERT_EQ(3U, [buttonSubviews count]);
185 // There should be a separator.
186 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
188 // There should be two "other profiles" items. The items are drawn from the
189 // bottom up, so in the opposite order of those in the AvatarMenu.
190 int profileIndex = 1;
191 for (int i = 5; i >= 2; i -= 2) {
192 // Each profile button has a separator.
193 EXPECT_TRUE([[subviews objectAtIndex:i] isKindOfClass:[NSBox class]]);
195 NSButton* button = base::mac::ObjCCast<NSButton>(
196 [subviews objectAtIndex:i-1]);
197 EXPECT_EQ(menu()->GetItemAt(profileIndex).name,
198 base::SysNSStringToUTF16([button title]));
199 EXPECT_EQ(profileIndex, [button tag]);
200 EXPECT_EQ(@selector(switchToProfile:), [button action]);
201 EXPECT_EQ(controller(), [button target]);
205 // There should be the profile avatar, name and links container in the active
206 // card view. The links displayed in the container are checked separately.
207 NSArray* activeCardSubviews = [[subviews objectAtIndex:6] subviews];
208 ASSERT_EQ(3U, [activeCardSubviews count]);
211 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
212 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSImageView class]]);
215 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
216 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
217 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
218 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
220 // Profile links. This is a local profile, so there should be a signin button
221 // and a signin promo. These are also tested in InitialLayoutWithNewMenu.
222 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
223 EXPECT_EQ(2U, [linksSubviews count]);
226 TEST_F(ProfileChooserControllerTest, OtherProfilesSortedAlphabetically) {
227 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
228 EnableFastUserSwitching();
230 // Add two extra profiles, to make sure sorting is alphabetical and not
231 // by order of creation.
232 testing_profile_manager()->
233 CreateTestingProfile("test3", scoped_ptr<PrefServiceSyncable>(),
234 base::ASCIIToUTF16("New Profile"), 1, std::string(),
235 TestingProfile::TestingFactories());
236 testing_profile_manager()->
237 CreateTestingProfile("test4", scoped_ptr<PrefServiceSyncable>(),
238 base::ASCIIToUTF16("Another Test"), 1, std::string(),
239 TestingProfile::TestingFactories());
240 StartProfileChooserController();
242 NSArray* subviews = [[[controller() window] contentView] subviews];
243 ASSERT_EQ(1U, [subviews count]);
244 subviews = [[subviews objectAtIndex:0] subviews];
245 NSString* sortedNames[] = { @"Another Test",
249 // There are four "other" profiles, each with a button and a separator, an
250 // active profile card, and an option buttons view with a separator. We
251 // also have an update promo for the new avatar menu.
252 // TODO(noms): Enforcing 12U fails on the waterfall debug bots, but it's not
253 // reproducible anywhere else.
254 ASSERT_GE([subviews count], 11U);
255 // There should be four "other profiles" items, sorted alphabetically. The
256 // "other profiles" start at index 2 (after the option buttons view and its
257 // separator), and each have a separator. We need to iterate through the
258 // profiles in the order displayed in the bubble, which is opposite from the
260 int sortedNameIndex = 0;
261 for (int i = 9; i >= 2; i -= 2) {
262 // The item at index i is the separator.
263 NSButton* button = base::mac::ObjCCast<NSButton>(
264 [subviews objectAtIndex:i-1]);
266 [[button title] isEqualToString:sortedNames[sortedNameIndex++]]);
270 TEST_F(ProfileChooserControllerTest,
271 LocalProfileActiveCardLinksWithNewMenu) {
272 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
273 StartProfileChooserController();
274 NSArray* subviews = [[[controller() window] contentView] subviews];
275 ASSERT_EQ(1U, [subviews count]);
276 subviews = [[subviews objectAtIndex:0] subviews];
277 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
278 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
280 ASSERT_EQ(2U, [activeCardLinks count]);
282 // There should be a sign in button.
283 NSButton* link = base::mac::ObjCCast<NSButton>(
284 [activeCardLinks objectAtIndex:0]);
285 EXPECT_EQ(@selector(showInlineSigninPage:), [link action]);
286 EXPECT_EQ(controller(), [link target]);
288 // Local profiles have a signin promo.
289 NSTextField* promo = base::mac::ObjCCast<NSTextField>(
290 [activeCardLinks objectAtIndex:1]);
291 EXPECT_GT([[promo stringValue] length], 0U);
294 TEST_F(ProfileChooserControllerTest,
295 SignedInProfileActiveCardLinksWithAccountConsistency) {
296 switches::EnableAccountConsistencyForTesting(
297 CommandLine::ForCurrentProcess());
298 // Sign in the first profile.
299 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
300 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
302 StartProfileChooserController();
303 NSArray* subviews = [[[controller() window] contentView] subviews];
304 ASSERT_EQ(1U, [subviews count]);
305 subviews = [[subviews objectAtIndex:0] subviews];
306 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
307 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
309 // There is one link: manage accounts.
310 ASSERT_EQ(1U, [activeCardLinks count]);
311 NSButton* manageAccountsLink =
312 base::mac::ObjCCast<NSButton>([activeCardLinks objectAtIndex:0]);
313 EXPECT_EQ(@selector(showAccountManagement:), [manageAccountsLink action]);
314 EXPECT_EQ(controller(), [manageAccountsLink target]);
317 TEST_F(ProfileChooserControllerTest,
318 SignedInProfileActiveCardLinksWithNewMenu) {
319 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
320 // Sign in the first profile.
321 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
322 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
324 StartProfileChooserController();
325 NSArray* subviews = [[[controller() window] contentView] subviews];
326 ASSERT_EQ(1U, [subviews count]);
327 subviews = [[subviews objectAtIndex:0] subviews];
328 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
329 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
331 // There is one disabled button with the user's email.
332 ASSERT_EQ(1U, [activeCardLinks count]);
333 NSButton* emailButton =
334 base::mac::ObjCCast<NSButton>([activeCardLinks objectAtIndex:0]);
335 EXPECT_EQ(kEmail, base::SysNSStringToUTF8([emailButton title]));
336 EXPECT_EQ(nil, [emailButton action]);
337 EXPECT_FALSE([emailButton isEnabled]);
340 TEST_F(ProfileChooserControllerTest, AccountManagementLayout) {
341 switches::EnableAccountConsistencyForTesting(
342 CommandLine::ForCurrentProcess());
343 // Sign in the first profile.
344 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
345 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
347 // Set up the signin manager and the OAuth2Tokens.
348 Profile* profile = browser()->profile();
349 SigninManagerFactory::GetForProfile(profile)->
350 SetAuthenticatedUsername(kEmail);
351 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
352 UpdateCredentials(kEmail, kLoginToken);
353 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
354 UpdateCredentials(kSecondaryEmail, kLoginToken);
356 StartProfileChooserController();
357 [controller() initMenuContentsWithView:
358 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
360 NSArray* subviews = [[[controller() window] contentView] subviews];
361 ASSERT_EQ(1U, [subviews count]);
362 subviews = [[subviews objectAtIndex:0] subviews];
364 // There should be one active card, one accounts container, two separators
365 // and one option buttons view.
366 ASSERT_EQ(5U, [subviews count]);
368 // There should be three buttons and two separators in the option
370 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
371 ASSERT_EQ(5U, [buttonSubviews count]);
373 // There should be a lock button.
374 NSButton* lockButton =
375 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
376 EXPECT_EQ(@selector(lockProfile:), [lockButton action]);
377 EXPECT_EQ(controller(), [lockButton target]);
379 // There should be a separator.
380 EXPECT_TRUE([[buttonSubviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
382 // There should be an incognito button.
383 NSButton* incognitoButton =
384 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:2]);
385 EXPECT_EQ(@selector(goIncognito:), [incognitoButton action]);
386 EXPECT_EQ(controller(), [incognitoButton target]);
388 // There should be a separator.
389 EXPECT_TRUE([[subviews objectAtIndex:3] isKindOfClass:[NSBox class]]);
391 // There should be a user switcher button.
392 NSButton* userSwitcherButton =
393 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:4]);
394 EXPECT_EQ(@selector(showUserManager:), [userSwitcherButton action]);
395 EXPECT_EQ(controller(), [userSwitcherButton target]);
397 // In the accounts view, there should be the account list container
398 // accounts and one "add accounts" button.
399 NSArray* accountsSubviews = [[subviews objectAtIndex:2] subviews];
400 ASSERT_EQ(2U, [accountsSubviews count]);
402 NSButton* addAccountsButton =
403 base::mac::ObjCCast<NSButton>([accountsSubviews objectAtIndex:0]);
404 EXPECT_EQ(@selector(addAccount:), [addAccountsButton action]);
405 EXPECT_EQ(controller(), [addAccountsButton target]);
407 // There should be two accounts in the account list container.
408 NSArray* accountsListSubviews = [[accountsSubviews objectAtIndex:1] subviews];
409 ASSERT_EQ(2U, [accountsListSubviews count]);
411 NSButton* genericAccount =
412 base::mac::ObjCCast<NSButton>([accountsListSubviews objectAtIndex:0]);
413 NSButton* genericAccountDelete = base::mac::ObjCCast<NSButton>(
414 [[genericAccount subviews] objectAtIndex:0]);
415 EXPECT_EQ(@selector(showAccountRemovalView:), [genericAccountDelete action]);
416 EXPECT_EQ(controller(), [genericAccountDelete target]);
417 EXPECT_NE(-1, [genericAccountDelete tag]);
419 // Primary accounts are always last.
420 NSButton* primaryAccount =
421 base::mac::ObjCCast<NSButton>([accountsListSubviews objectAtIndex:1]);
422 NSButton* primaryAccountDelete = base::mac::ObjCCast<NSButton>(
423 [[primaryAccount subviews] objectAtIndex:0]);
424 EXPECT_EQ(@selector(showAccountRemovalView:), [primaryAccountDelete action]);
425 EXPECT_EQ(controller(), [primaryAccountDelete target]);
426 EXPECT_EQ(-1, [primaryAccountDelete tag]);
428 // There should be another separator.
429 EXPECT_TRUE([[subviews objectAtIndex:3] isKindOfClass:[NSBox class]]);
431 // There should be the profile avatar, name and a "hide accounts" link
432 // container in the active card view.
433 NSArray* activeCardSubviews = [[subviews objectAtIndex:4] subviews];
434 ASSERT_EQ(3U, [activeCardSubviews count]);
437 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
438 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSImageView class]]);
441 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
442 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
443 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
444 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
446 // Profile links. This is a local profile, so there should be a signin button.
447 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
448 ASSERT_EQ(1U, [linksSubviews count]);
449 NSButton* link = base::mac::ObjCCast<NSButton>(
450 [linksSubviews objectAtIndex:0]);
451 EXPECT_EQ(@selector(hideAccountManagement:), [link action]);
452 EXPECT_EQ(controller(), [link target]);