// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/profiles/profile_menu_controller.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "testing/gtest_mac.h"
#include "ui/base/l10n/l10n_util_mac.h"
class ProfileMenuControllerTest : public BrowserWithTestWindowTest {
public:
void SetUp() override {
CocoaTest::BootstrapCocoa();
BrowserWithTestWindowTest::SetUp();
RebuildController();
}
void TearDown() override {
[controller_ deinitialize];
controller_ = nil;
item_ = nil;
BrowserWithTestWindowTest::TearDown();
}
void TestBottomItems() {
NSMenu* menu = controller().menu;
NSInteger count = menu.numberOfItems;
ASSERT_GE(count, 4);
NSMenuItem* item = [menu itemAtIndex:count - 4];
EXPECT_TRUE(item.isSeparatorItem);
item = [menu itemAtIndex:count - 3];
EXPECT_EQ(@selector(editProfile:), item.action);
item = [menu itemAtIndex:count - 2];
EXPECT_TRUE(item.isSeparatorItem);
item = [menu itemAtIndex:count - 1];
EXPECT_EQ(@selector(newProfile:), item.action);
}
void VerifyProfileNamedIsActive(NSString* title, int line) {
for (NSMenuItem* item in controller().menu.itemArray) {
if ([item.title isEqualToString:title]) {
EXPECT_EQ(NSControlStateValueOn, item.state)
<< base::SysNSStringToUTF8(item.title) << " (from line " << line
<< ")";
} else {
EXPECT_EQ(NSControlStateValueOff, item.state)
<< base::SysNSStringToUTF8(item.title) << " (from line " << line
<< ")";
}
}
}
ProfileMenuController* controller() { return controller_; }
NSMenuItem* menu_item() { return item_; }
protected:
void RebuildController() {
item_ = [[NSMenuItem alloc] initWithTitle:@"Users"
action:nil
keyEquivalent:@""];
controller_ = [[ProfileMenuController alloc]
initSynchronouslyForTestingWithMainMenuItem:item_
profileAttributesStorage:
profile_manager()->profile_attributes_storage()];
}
private:
NSMenuItem* __strong item_;
ProfileMenuController* __strong controller_;
};
TEST_F(ProfileMenuControllerTest, InitializeMenu) {
NSMenu* menu = controller().menu;
// Profile, <sep>, Edit, <sep>, New.
ASSERT_EQ(5, menu.numberOfItems);
TestBottomItems();
EXPECT_FALSE(menu_item().hidden);
}
TEST_F(ProfileMenuControllerTest, CreateItemWithTitle) {
NSMenuItem* item =
[controller() createItemWithTitle:@"Title"
action:@selector(someSelector:)];
EXPECT_NSEQ(@"Title", item.title);
EXPECT_EQ(controller(), item.target);
EXPECT_EQ(@selector(someSelector:), item.action);
EXPECT_NSEQ(@"", item.keyEquivalent);
}
TEST_F(ProfileMenuControllerTest, RebuildMenu) {
NSMenu* menu = controller().menu;
EXPECT_EQ(5, menu.numberOfItems);
EXPECT_FALSE(menu_item().hidden);
// Create some more profiles on the manager.
TestingProfileManager* manager = profile_manager();
manager->CreateTestingProfile("Profile 2");
manager->CreateTestingProfile("Profile 3");
// Verify that the menu got rebuilt.
ASSERT_EQ(7, menu.numberOfItems);
NSMenuItem* item = [menu itemAtIndex:0];
EXPECT_EQ(@selector(switchToProfileFromMenu:), item.action);
EXPECT_TRUE([controller() validateMenuItem:item]);
item = [menu itemAtIndex:1];
EXPECT_EQ(@selector(switchToProfileFromMenu:), item.action);
EXPECT_TRUE([controller() validateMenuItem:item]);
item = [menu itemAtIndex:2];
EXPECT_EQ(@selector(switchToProfileFromMenu:), item.action);
EXPECT_TRUE([controller() validateMenuItem:item]);
TestBottomItems();
EXPECT_FALSE(menu_item().hidden);
}
TEST_F(ProfileMenuControllerTest, InsertItems) {
NSMenu* menu = [[NSMenu alloc] initWithTitle:@""];
ASSERT_EQ(0, menu.numberOfItems);
// Even with one profile items can still be inserted.
BOOL result = [controller() insertItemsIntoMenu:menu atOffset:0 fromDock:NO];
EXPECT_TRUE(result);
EXPECT_EQ(1, menu.numberOfItems);
[menu removeAllItems];
// Same for use in building the dock menu.
result = [controller() insertItemsIntoMenu:menu atOffset:0 fromDock:YES];
EXPECT_FALSE(result);
EXPECT_EQ(0, menu.numberOfItems);
[menu removeAllItems];
// Create one more profile on the manager.
TestingProfileManager* manager = profile_manager();
manager->CreateTestingProfile("Profile 2");
// With more than one profile, insertItems should return YES.
result = [controller() insertItemsIntoMenu:menu atOffset:0 fromDock:NO];
EXPECT_TRUE(result);
ASSERT_EQ(2, menu.numberOfItems);
NSMenuItem* item = [menu itemAtIndex:0];
EXPECT_EQ(@selector(switchToProfileFromMenu:), item.action);
item = [menu itemAtIndex:1];
EXPECT_EQ(@selector(switchToProfileFromMenu:), item.action);
[menu removeAllItems];
// And for the dock, the selector should be different and there should be a
// header item.
result = [controller() insertItemsIntoMenu:menu atOffset:0 fromDock:YES];
EXPECT_TRUE(result);
ASSERT_EQ(3, menu.numberOfItems);
// First item is a label item.
item = [menu itemAtIndex:0];
EXPECT_FALSE([item isEnabled]);
item = [menu itemAtIndex:1];
EXPECT_EQ(@selector(switchToProfileFromDock:), item.action);
item = [menu itemAtIndex:2];
EXPECT_EQ(@selector(switchToProfileFromDock:), item.action);
}
TEST_F(ProfileMenuControllerTest, InitialActiveBrowser) {
[controller() activeBrowserChangedTo:nullptr];
VerifyProfileNamedIsActive(l10n_util::GetNSString(IDS_DEFAULT_PROFILE_NAME),
__LINE__);
}
// Note: BrowserList::SetLastActive() is typically called as part of
// BrowserWindow::Show() and when a Browser becomes active. We don't need a full
// BrowserWindow, so it is called manually.
TEST_F(ProfileMenuControllerTest, SetActiveAndRemove) {
// Set the name of the default profile, so that's it's not empty.
const std::u16string kDefaultProfileName = u"DefaultProfile";
profile_manager()
->profile_attributes_storage()
->GetProfileAttributesWithPath(browser()->profile()->GetPath())
->SetLocalProfileName(kDefaultProfileName, false);
NSMenu* menu = controller().menu;
TestingProfileManager* manager = profile_manager();
TestingProfile* profile2 = manager->CreateTestingProfile("Profile 2");
TestingProfile* profile3 = manager->CreateTestingProfile("Profile 3");
ASSERT_EQ(7, menu.numberOfItems);
// Create a browser and "show" it.
Browser::CreateParams profile2_params(profile2, true);
std::unique_ptr<Browser> p2_browser(
CreateBrowserWithTestWindowForParams(profile2_params));
[controller() activeBrowserChangedTo:p2_browser.get()];
VerifyProfileNamedIsActive(@"Profile 2", __LINE__);
// Close the browser and make sure the new active browser's profile is active.
p2_browser.reset();
[controller() activeBrowserChangedTo:browser()];
VerifyProfileNamedIsActive(base::SysUTF16ToNSString(kDefaultProfileName),
__LINE__);
// Open a new browser and make sure it takes effect.
Browser::CreateParams profile3_params(profile3, true);
std::unique_ptr<Browser> p3_browser(
CreateBrowserWithTestWindowForParams(profile3_params));
[controller() activeBrowserChangedTo:p3_browser.get()];
VerifyProfileNamedIsActive(@"Profile 3", __LINE__);
// Close the browser and make sure the new active browser's profile is active.
p3_browser.reset();
[controller() activeBrowserChangedTo:browser()];
VerifyProfileNamedIsActive(base::SysUTF16ToNSString(kDefaultProfileName),
__LINE__);
// Close the browser.
std::unique_ptr<Browser> browser = release_browser();
browser->tab_strip_model()->CloseAllTabs();
browser.reset();
std::unique_ptr<BrowserWindow> browser_window = release_browser_window();
browser_window->Close();
browser_window.reset();
EXPECT_TRUE(BrowserList::GetInstance()->empty());
[controller() activeBrowserChangedTo:nil];
VerifyProfileNamedIsActive(base::SysUTF16ToNSString(kDefaultProfileName),
__LINE__);
}
TEST_F(ProfileMenuControllerTest, DeleteActiveProfile) {
TestingProfileManager* manager = profile_manager();
manager->CreateTestingProfile("Profile 2");
TestingProfile* profile3 = manager->CreateTestingProfile("Profile 3");
ASSERT_EQ(3U, manager->profile_manager()->GetNumberOfProfiles());
const base::FilePath profile3_path = profile3->GetPath();
manager->DeleteTestingProfile("Profile 3");
// Simulate an unloaded profile by setting the "last used" local state pref
// the profile that was just deleted.
ScopedTestingLocalState* local_state = manager->local_state();
local_state->Get()->SetUserPref(
prefs::kProfileLastUsed,
base::Value(profile3_path.BaseName().MaybeAsASCII()));
EXPECT_FALSE(ProfileManager::GetLastUsedProfileIfLoaded());
// Simulate the active browser changing to NULL and ensure a profile doesn't
// get created by disallowing IO operations temporarily.
base::ScopedDisallowBlocking scoped_disallow_blocking;
[controller() activeBrowserChangedTo:nullptr];
// Check that validateMenuItem does not load a profile, and edit is disabled.
// Adding a new profile is still possible since this happens through the
// profile picker.
NSMenu* menu = controller().menu;
for (NSMenuItem* item in [menu itemArray]) {
bool is_edit = item.action == @selector(editProfile:);
EXPECT_EQ([controller() validateMenuItem:item], !is_edit);
}
}
TEST_F(ProfileMenuControllerTest, AddProfileDisabled) {
ScopedTestingLocalState* local_state = profile_manager()->local_state();
local_state->Get()->SetUserPref(prefs::kBrowserAddPersonEnabled,
base::Value(false));
RebuildController();
NSMenu* menu = controller().menu;
NSInteger count = menu.numberOfItems;
ASSERT_GE(count, 2);
NSMenuItem* item = [menu itemAtIndex:count - 2];
EXPECT_TRUE(item.isSeparatorItem);
item = [menu itemAtIndex:count - 1];
EXPECT_EQ(@selector(editProfile:), item.action);
}