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/services/gcm/fake_gcm_profile_service.h"
17 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
18 #include "chrome/browser/signin/account_tracker_service_factory.h"
19 #include "chrome/browser/signin/fake_account_tracker_service.h"
20 #include "chrome/browser/signin/fake_profile_oauth2_token_service.h"
21 #include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h"
22 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
23 #include "chrome/browser/signin/signin_header_helper.h"
24 #include "chrome/browser/signin/signin_manager_factory.h"
25 #include "chrome/browser/ui/browser.h"
26 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/common/pref_names.h"
29 #include "components/signin/core/browser/profile_oauth2_token_service.h"
30 #include "components/signin/core/browser/signin_manager.h"
31 #include "components/signin/core/common/profile_management_switches.h"
33 const std::string kEmail = "user@gmail.com";
34 const std::string kSecondaryEmail = "user2@gmail.com";
35 const std::string kLoginToken = "oauth2_login_token";
37 class ProfileChooserControllerTest : public CocoaProfileTest {
39 ProfileChooserControllerTest() {
42 virtual void SetUp() override {
43 CocoaProfileTest::SetUp();
44 ASSERT_TRUE(browser()->profile());
46 AccountTrackerServiceFactory::GetInstance()->SetTestingFactory(
47 browser()->profile(), FakeAccountTrackerService::Build);
48 gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactory(
49 browser()->profile(), gcm::FakeGCMProfileService::Build);
51 TestingProfile::TestingFactories factories;
53 std::make_pair(ProfileOAuth2TokenServiceFactory::GetInstance(),
54 BuildFakeProfileOAuth2TokenService));
56 std::make_pair(AccountTrackerServiceFactory::GetInstance(),
57 FakeAccountTrackerService::Build));
58 testing_profile_manager()->
59 CreateTestingProfile("test1", scoped_ptr<PrefServiceSyncable>(),
60 base::ASCIIToUTF16("Test 1"), 0, std::string(),
62 testing_profile_manager()->
63 CreateTestingProfile("test2", scoped_ptr<PrefServiceSyncable>(),
64 base::ASCIIToUTF16("Test 2"), 1, std::string(),
65 TestingProfile::TestingFactories());
67 menu_ = new AvatarMenu(testing_profile_manager()->profile_info_cache(),
71 // There should be the default profile + two profiles we created.
72 EXPECT_EQ(3U, menu_->GetNumberOfItems());
75 virtual void TearDown() override {
78 CocoaProfileTest::TearDown();
81 void StartProfileChooserController() {
82 NSRect frame = [test_window() frame];
83 NSPoint point = NSMakePoint(NSMidX(frame), NSMidY(frame));
84 controller_.reset([[ProfileChooserController alloc]
85 initWithBrowser:browser()
87 viewMode:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER
88 tutorialMode:profiles::TUTORIAL_MODE_NONE
89 serviceType:signin::GAIA_SERVICE_TYPE_NONE]);
90 [controller_ showWindow:nil];
93 void EnableFastUserSwitching() {
94 CommandLine::ForCurrentProcess()->AppendSwitch(
95 switches::kFastUserSwitching);
98 ProfileChooserController* controller() { return controller_; }
99 AvatarMenu* menu() { return menu_; }
102 base::scoped_nsobject<ProfileChooserController> controller_;
104 // Weak; owned by |controller_|.
107 DISALLOW_COPY_AND_ASSIGN(ProfileChooserControllerTest);
110 TEST_F(ProfileChooserControllerTest, InitialLayoutWithNewMenu) {
111 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
112 StartProfileChooserController();
114 NSArray* subviews = [[[controller() window] contentView] subviews];
115 ASSERT_EQ(2U, [subviews count]);
116 subviews = [[subviews objectAtIndex:0] subviews];
118 // Three profiles means we should have one active card, one separator and
119 // one option buttons view. We also have an update promo for the new avatar
121 // TODO(noms): Enforcing 4U fails on the waterfall debug bots, but it's not
122 // reproducible anywhere else.
123 ASSERT_GE([subviews count], 3U);
125 // There should be two buttons and a separator in the option buttons view.
126 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
127 ASSERT_EQ(3U, [buttonSubviews count]);
129 // There should be an incognito button.
130 NSButton* incognitoButton =
131 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
132 EXPECT_EQ(@selector(goIncognito:), [incognitoButton action]);
133 EXPECT_EQ(controller(), [incognitoButton target]);
135 // There should be a separator.
136 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
138 // There should be a user switcher button.
139 NSButton* userSwitcherButton =
140 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:2]);
141 EXPECT_EQ(@selector(showUserManager:), [userSwitcherButton action]);
142 EXPECT_EQ(controller(), [userSwitcherButton target]);
144 // There should be a separator.
145 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
147 // There should be the profile avatar, name and links container in the active
148 // card view. The links displayed in the container are checked separately.
149 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
150 ASSERT_EQ(3U, [activeCardSubviews count]);
153 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
154 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSButton class]]);
157 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
158 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
159 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
160 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
162 // Profile links. This is a local profile, so there should be a signin button
163 // and a signin promo.
164 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
165 ASSERT_EQ(2U, [linksSubviews count]);
166 NSButton* link = base::mac::ObjCCast<NSButton>(
167 [linksSubviews objectAtIndex:0]);
168 EXPECT_EQ(@selector(showInlineSigninPage:), [link action]);
169 EXPECT_EQ(controller(), [link target]);
171 NSTextField* promo = base::mac::ObjCCast<NSTextField>(
172 [linksSubviews objectAtIndex:1]);
173 EXPECT_GT([[promo stringValue] length], 0U);
176 TEST_F(ProfileChooserControllerTest, InitialLayoutWithFastUserSwitcher) {
177 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
178 EnableFastUserSwitching();
179 StartProfileChooserController();
181 NSArray* subviews = [[[controller() window] contentView] subviews];
182 ASSERT_EQ(2U, [subviews count]);
183 subviews = [[subviews objectAtIndex:0] subviews];
185 // Three profiles means we should have one active card and a
186 // fast user switcher which has two "other" profiles and 2 separators, and
187 // an option buttons view with its separator. We also have a promo for
188 // the new avatar menu.
189 // TODO(noms): Enforcing 8U fails on the waterfall debug bots, but it's not
190 // reproducible anywhere else.
191 ASSERT_GE([subviews count], 7U);
193 // There should be two buttons and a separator in the option buttons view.
194 // These buttons are tested in InitialLayoutWithNewMenu.
195 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
196 ASSERT_EQ(3U, [buttonSubviews count]);
198 // There should be a separator.
199 EXPECT_TRUE([[subviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
201 // There should be two "other profiles" items. The items are drawn from the
202 // bottom up, so in the opposite order of those in the AvatarMenu.
203 int profileIndex = 1;
204 for (int i = 5; i >= 2; i -= 2) {
205 // Each profile button has a separator.
206 EXPECT_TRUE([[subviews objectAtIndex:i] isKindOfClass:[NSBox class]]);
208 NSButton* button = base::mac::ObjCCast<NSButton>(
209 [subviews objectAtIndex:i-1]);
210 EXPECT_EQ(menu()->GetItemAt(profileIndex).name,
211 base::SysNSStringToUTF16([button title]));
212 EXPECT_EQ(profileIndex, [button tag]);
213 EXPECT_EQ(@selector(switchToProfile:), [button action]);
214 EXPECT_EQ(controller(), [button target]);
218 // There should be the profile avatar, name and links container in the active
219 // card view. The links displayed in the container are checked separately.
220 NSArray* activeCardSubviews = [[subviews objectAtIndex:6] subviews];
221 ASSERT_EQ(3U, [activeCardSubviews count]);
224 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
225 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSButton class]]);
228 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
229 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
230 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
231 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
233 // Profile links. This is a local profile, so there should be a signin button
234 // and a signin promo. These are also tested in InitialLayoutWithNewMenu.
235 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
236 EXPECT_EQ(2U, [linksSubviews count]);
239 TEST_F(ProfileChooserControllerTest, OtherProfilesSortedAlphabetically) {
240 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
241 EnableFastUserSwitching();
243 // Add two extra profiles, to make sure sorting is alphabetical and not
244 // by order of creation.
245 testing_profile_manager()->
246 CreateTestingProfile("test3", scoped_ptr<PrefServiceSyncable>(),
247 base::ASCIIToUTF16("New Profile"), 1, std::string(),
248 TestingProfile::TestingFactories());
249 testing_profile_manager()->
250 CreateTestingProfile("test4", scoped_ptr<PrefServiceSyncable>(),
251 base::ASCIIToUTF16("Another Test"), 1, std::string(),
252 TestingProfile::TestingFactories());
253 StartProfileChooserController();
255 NSArray* subviews = [[[controller() window] contentView] subviews];
256 ASSERT_EQ(2U, [subviews count]);
257 subviews = [[subviews objectAtIndex:0] subviews];
258 NSString* sortedNames[] = { @"Another Test",
262 // There are four "other" profiles, each with a button and a separator, an
263 // active profile card, and an option buttons view with a separator. We
264 // also have an update promo for the new avatar menu.
265 // TODO(noms): Enforcing 12U fails on the waterfall debug bots, but it's not
266 // reproducible anywhere else.
267 ASSERT_GE([subviews count], 11U);
268 // There should be four "other profiles" items, sorted alphabetically. The
269 // "other profiles" start at index 2 (after the option buttons view and its
270 // separator), and each have a separator. We need to iterate through the
271 // profiles in the order displayed in the bubble, which is opposite from the
273 int sortedNameIndex = 0;
274 for (int i = 9; i >= 2; i -= 2) {
275 // The item at index i is the separator.
276 NSButton* button = base::mac::ObjCCast<NSButton>(
277 [subviews objectAtIndex:i-1]);
279 [[button title] isEqualToString:sortedNames[sortedNameIndex++]]);
283 TEST_F(ProfileChooserControllerTest,
284 LocalProfileActiveCardLinksWithNewMenu) {
285 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
286 StartProfileChooserController();
287 NSArray* subviews = [[[controller() window] contentView] subviews];
288 ASSERT_EQ(2U, [subviews count]);
289 subviews = [[subviews objectAtIndex:0] subviews];
290 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
291 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
293 ASSERT_EQ(2U, [activeCardLinks count]);
295 // There should be a sign in button.
296 NSButton* link = base::mac::ObjCCast<NSButton>(
297 [activeCardLinks objectAtIndex:0]);
298 EXPECT_EQ(@selector(showInlineSigninPage:), [link action]);
299 EXPECT_EQ(controller(), [link target]);
301 // Local profiles have a signin promo.
302 NSTextField* promo = base::mac::ObjCCast<NSTextField>(
303 [activeCardLinks objectAtIndex:1]);
304 EXPECT_GT([[promo stringValue] length], 0U);
307 TEST_F(ProfileChooserControllerTest,
308 SignedInProfileActiveCardLinksWithAccountConsistency) {
309 switches::EnableAccountConsistencyForTesting(
310 CommandLine::ForCurrentProcess());
311 // Sign in the first profile.
312 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
313 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
315 StartProfileChooserController();
316 NSArray* subviews = [[[controller() window] contentView] subviews];
317 ASSERT_EQ(2U, [subviews count]);
318 subviews = [[subviews objectAtIndex:0] subviews];
319 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
320 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
322 // There is one link: manage accounts.
323 ASSERT_EQ(1U, [activeCardLinks count]);
324 NSButton* manageAccountsLink =
325 base::mac::ObjCCast<NSButton>([activeCardLinks objectAtIndex:0]);
326 EXPECT_EQ(@selector(showAccountManagement:), [manageAccountsLink action]);
327 EXPECT_EQ(controller(), [manageAccountsLink target]);
330 TEST_F(ProfileChooserControllerTest,
331 SignedInProfileActiveCardLinksWithNewMenu) {
332 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
333 // Sign in the first profile.
334 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
335 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
337 StartProfileChooserController();
338 NSArray* subviews = [[[controller() window] contentView] subviews];
339 ASSERT_EQ(2U, [subviews count]);
340 subviews = [[subviews objectAtIndex:0] subviews];
341 NSArray* activeCardSubviews = [[subviews objectAtIndex:2] subviews];
342 NSArray* activeCardLinks = [[activeCardSubviews objectAtIndex:0] subviews];
344 // There is one disabled button with the user's email.
345 ASSERT_EQ(1U, [activeCardLinks count]);
346 NSButton* emailButton =
347 base::mac::ObjCCast<NSButton>([activeCardLinks objectAtIndex:0]);
348 EXPECT_EQ(kEmail, base::SysNSStringToUTF8([emailButton title]));
349 EXPECT_EQ(nil, [emailButton action]);
350 EXPECT_FALSE([emailButton isEnabled]);
353 TEST_F(ProfileChooserControllerTest, AccountManagementLayout) {
354 switches::EnableAccountConsistencyForTesting(
355 CommandLine::ForCurrentProcess());
356 // Sign in the first profile.
357 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
358 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
360 // Mark that we are using the profile name on purpose, so that we don't
361 // fallback to testing the algorithm that chooses which default name
363 cache->SetProfileIsUsingDefaultNameAtIndex(0, false);
365 // Set up the signin manager and the OAuth2Tokens.
366 Profile* profile = browser()->profile();
367 SigninManagerFactory::GetForProfile(profile)->
368 SetAuthenticatedUsername(kEmail);
369 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
370 UpdateCredentials(kEmail, kLoginToken);
371 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
372 UpdateCredentials(kSecondaryEmail, kLoginToken);
374 StartProfileChooserController();
375 [controller() initMenuContentsWithView:
376 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
378 NSArray* subviews = [[[controller() window] contentView] subviews];
379 ASSERT_EQ(2U, [subviews count]);
380 subviews = [[subviews objectAtIndex:0] subviews];
382 // There should be one active card, one accounts container, two separators
383 // and one option buttons view.
384 ASSERT_EQ(5U, [subviews count]);
386 // There should be three buttons and two separators in the option
388 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
389 ASSERT_EQ(3U, [buttonSubviews count]);
391 // There should be a separator.
392 EXPECT_TRUE([[buttonSubviews objectAtIndex:1] isKindOfClass:[NSBox class]]);
394 // There should be an incognito button.
395 NSButton* incognitoButton =
396 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
397 EXPECT_EQ(@selector(goIncognito:), [incognitoButton action]);
398 EXPECT_EQ(controller(), [incognitoButton target]);
400 // There should be a separator.
401 EXPECT_TRUE([[subviews objectAtIndex:3] isKindOfClass:[NSBox class]]);
403 // There should be a user switcher button.
404 NSButton* userSwitcherButton =
405 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:2]);
406 EXPECT_EQ(@selector(showUserManager:), [userSwitcherButton action]);
407 EXPECT_EQ(controller(), [userSwitcherButton target]);
409 // In the accounts view, there should be the account list container
410 // accounts and one "add accounts" button.
411 NSArray* accountsSubviews = [[subviews objectAtIndex:2] subviews];
412 ASSERT_EQ(2U, [accountsSubviews count]);
414 NSButton* addAccountsButton =
415 base::mac::ObjCCast<NSButton>([accountsSubviews objectAtIndex:0]);
416 EXPECT_EQ(@selector(addAccount:), [addAccountsButton action]);
417 EXPECT_EQ(controller(), [addAccountsButton target]);
419 // There should be two accounts in the account list container.
420 NSArray* accountsListSubviews = [[accountsSubviews objectAtIndex:1] subviews];
421 ASSERT_EQ(2U, [accountsListSubviews count]);
423 NSButton* genericAccount =
424 base::mac::ObjCCast<NSButton>([accountsListSubviews objectAtIndex:0]);
425 NSButton* genericAccountDelete = base::mac::ObjCCast<NSButton>(
426 [[genericAccount subviews] objectAtIndex:0]);
427 EXPECT_EQ(@selector(showAccountRemovalView:), [genericAccountDelete action]);
428 EXPECT_EQ(controller(), [genericAccountDelete target]);
429 EXPECT_NE(-1, [genericAccountDelete tag]);
431 // Primary accounts are always last.
432 NSButton* primaryAccount =
433 base::mac::ObjCCast<NSButton>([accountsListSubviews objectAtIndex:1]);
434 NSButton* primaryAccountDelete = base::mac::ObjCCast<NSButton>(
435 [[primaryAccount subviews] objectAtIndex:0]);
436 EXPECT_EQ(@selector(showAccountRemovalView:), [primaryAccountDelete action]);
437 EXPECT_EQ(controller(), [primaryAccountDelete target]);
438 EXPECT_EQ(-1, [primaryAccountDelete tag]);
440 // There should be another separator.
441 EXPECT_TRUE([[subviews objectAtIndex:3] isKindOfClass:[NSBox class]]);
443 // There should be the profile avatar, name and a "hide accounts" link
444 // container in the active card view.
445 NSArray* activeCardSubviews = [[subviews objectAtIndex:4] subviews];
446 ASSERT_EQ(3U, [activeCardSubviews count]);
449 NSView* activeProfileImage = [activeCardSubviews objectAtIndex:2];
450 EXPECT_TRUE([activeProfileImage isKindOfClass:[NSButton class]]);
453 NSView* activeProfileName = [activeCardSubviews objectAtIndex:1];
454 EXPECT_TRUE([activeProfileName isKindOfClass:[NSButton class]]);
455 EXPECT_EQ(menu()->GetItemAt(0).name, base::SysNSStringToUTF16(
456 [base::mac::ObjCCast<NSButton>(activeProfileName) title]));
458 // Profile links. This is a local profile, so there should be a signin button.
459 NSArray* linksSubviews = [[activeCardSubviews objectAtIndex:0] subviews];
460 ASSERT_EQ(1U, [linksSubviews count]);
461 NSButton* link = base::mac::ObjCCast<NSButton>(
462 [linksSubviews objectAtIndex:0]);
463 EXPECT_EQ(@selector(hideAccountManagement:), [link action]);
464 EXPECT_EQ(controller(), [link target]);
467 TEST_F(ProfileChooserControllerTest, SignedInProfileLockDisabled) {
468 switches::EnableNewProfileManagementForTesting(
469 CommandLine::ForCurrentProcess());
470 // Sign in the first profile.
471 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
472 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
473 // The preference, not the email, determines whether the profile can lock.
474 browser()->profile()->GetPrefs()->SetString(
475 prefs::kGoogleServicesHostedDomain, "chromium.org");
477 StartProfileChooserController();
478 NSArray* subviews = [[[controller() window] contentView] subviews];
479 ASSERT_EQ(2U, [subviews count]);
480 subviews = [[subviews objectAtIndex:0] subviews];
482 // There will be two buttons and one separators in the option buttons view.
483 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
484 ASSERT_EQ(3U, [buttonSubviews count]);
486 // The last button should not be the lock button.
487 NSButton* lastButton =
488 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
489 ASSERT_TRUE(lastButton);
490 EXPECT_NE(@selector(lockProfile:), [lastButton action]);
493 TEST_F(ProfileChooserControllerTest, SignedInProfileLockEnabled) {
494 switches::EnableNewProfileManagementForTesting(
495 CommandLine::ForCurrentProcess());
496 // Sign in the first profile.
497 ProfileInfoCache* cache = testing_profile_manager()->profile_info_cache();
498 cache->SetUserNameOfProfileAtIndex(0, base::ASCIIToUTF16(kEmail));
499 // The preference, not the email, determines whether the profile can lock.
500 browser()->profile()->GetPrefs()->SetString(
501 prefs::kGoogleServicesHostedDomain, "google.com");
502 // Lock is only available where a supervised user is present.
503 cache->SetSupervisedUserIdOfProfileAtIndex(1, kEmail);
505 StartProfileChooserController();
506 NSArray* subviews = [[[controller() window] contentView] subviews];
507 ASSERT_EQ(2U, [subviews count]);
508 subviews = [[subviews objectAtIndex:0] subviews];
510 // There will be three buttons and two separators in the option buttons view.
511 NSArray* buttonSubviews = [[subviews objectAtIndex:0] subviews];
512 ASSERT_EQ(5U, [buttonSubviews count]);
514 // There should be a lock button.
515 NSButton* lockButton =
516 base::mac::ObjCCast<NSButton>([buttonSubviews objectAtIndex:0]);
517 ASSERT_TRUE(lockButton);
518 EXPECT_EQ(@selector(lockProfile:), [lockButton action]);
519 EXPECT_EQ(controller(), [lockButton target]);
520 EXPECT_TRUE([lockButton isEnabled]);