1 // Copyright 2013 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 "ui/app_list/cocoa/apps_search_box_controller.h" 6 7 #include "base/mac/scoped_nsobject.h" 8 #include "base/strings/sys_string_conversions.h" 9 #include "base/strings/utf_string_conversions.h" 10 #import "testing/gtest_mac.h" 11 #include "ui/app_list/app_list_menu.h" 12 #include "ui/app_list/app_list_model_observer.h" 13 #include "ui/app_list/search_box_model.h" 14 #include "ui/app_list/test/app_list_test_model.h" 15 #include "ui/app_list/test/app_list_test_view_delegate.h" 16 #import "ui/base/cocoa/menu_controller.h" 17 #import "ui/gfx/test/ui_cocoa_test_helper.h" 18 19 using base::ASCIIToUTF16; 20 21 @interface TestAppsSearchBoxDelegate : NSObject<AppsSearchBoxDelegate> { 22 @private 23 app_list::SearchBoxModel searchBoxModel_; 24 app_list::test::AppListTestViewDelegate appListDelegate_; 25 app_list::test::AppListTestModel appListModel_; 26 int textChangeCount_; 27 } 28 29 @property(assign, nonatomic) int textChangeCount; 30 31 @end 32 33 @implementation TestAppsSearchBoxDelegate 34 35 @synthesize textChangeCount = textChangeCount_; 36 37 - (id)init { 38 if ((self = [super init])) { 39 app_list::AppListViewDelegate::Users users(2); 40 users[0].name = ASCIIToUTF16("user1"); 41 users[1].name = ASCIIToUTF16("user2"); 42 users[1].email = ASCIIToUTF16("user2 (a] chromium.org"); 43 users[1].active = true; 44 appListDelegate_.SetUsers(users); 45 } 46 return self; 47 } 48 49 - (app_list::SearchBoxModel*)searchBoxModel { 50 return &searchBoxModel_; 51 } 52 53 - (app_list::AppListViewDelegate*)appListDelegate { 54 return &appListDelegate_; 55 } 56 57 - (app_list::test::AppListTestViewDelegate*)appListTestViewDelegate { 58 return &appListDelegate_; 59 } 60 61 62 - (BOOL)control:(NSControl*)control 63 textView:(NSTextView*)textView 64 doCommandBySelector:(SEL)command { 65 return NO; 66 } 67 68 - (void)modelTextDidChange { 69 ++textChangeCount_; 70 } 71 72 - (CGFloat)bubbleCornerRadius { 73 return 3; 74 } 75 76 - (app_list::AppListModel*)appListModel { 77 return &appListModel_; 78 } 79 80 @end 81 82 namespace app_list { 83 namespace test { 84 85 class AppsSearchBoxControllerTest : public ui::CocoaTest { 86 public: 87 AppsSearchBoxControllerTest() { 88 Init(); 89 } 90 91 virtual void SetUp() OVERRIDE { 92 apps_search_box_controller_.reset( 93 [[AppsSearchBoxController alloc] initWithFrame: 94 NSMakeRect(0, 0, 400, 100)]); 95 delegate_.reset([[TestAppsSearchBoxDelegate alloc] init]); 96 [apps_search_box_controller_ setDelegate:delegate_]; 97 98 ui::CocoaTest::SetUp(); 99 [[test_window() contentView] addSubview:[apps_search_box_controller_ view]]; 100 } 101 102 virtual void TearDown() OVERRIDE { 103 [apps_search_box_controller_ setDelegate:nil]; 104 ui::CocoaTest::TearDown(); 105 } 106 107 void SimulateKeyAction(SEL c) { 108 NSControl* control = [apps_search_box_controller_ searchTextField]; 109 [apps_search_box_controller_ control:control 110 textView:nil 111 doCommandBySelector:c]; 112 } 113 114 protected: 115 base::scoped_nsobject<TestAppsSearchBoxDelegate> delegate_; 116 base::scoped_nsobject<AppsSearchBoxController> apps_search_box_controller_; 117 118 private: 119 DISALLOW_COPY_AND_ASSIGN(AppsSearchBoxControllerTest); 120 }; 121 122 TEST_VIEW(AppsSearchBoxControllerTest, [apps_search_box_controller_ view]); 123 124 // Test the search box initialization, and search input and clearing. 125 TEST_F(AppsSearchBoxControllerTest, SearchBoxModel) { 126 app_list::SearchBoxModel* model = [delegate_ searchBoxModel]; 127 // Usually localized "Search". 128 const base::string16 hit_text(ASCIIToUTF16("hint")); 129 model->SetHintText(hit_text); 130 EXPECT_NSEQ(base::SysUTF16ToNSString(hit_text), 131 [[[apps_search_box_controller_ searchTextField] cell] placeholderString]); 132 133 const base::string16 search_text(ASCIIToUTF16("test")); 134 model->SetText(search_text); 135 EXPECT_NSEQ(base::SysUTF16ToNSString(search_text), 136 [[apps_search_box_controller_ searchTextField] stringValue]); 137 // Updates coming via the model should notify the delegate. 138 EXPECT_EQ(1, [delegate_ textChangeCount]); 139 140 // Updates from the view should update the model and notify the delegate. 141 [apps_search_box_controller_ clearSearch]; 142 EXPECT_EQ(base::string16(), model->text()); 143 EXPECT_NSEQ([NSString string], 144 [[apps_search_box_controller_ searchTextField] stringValue]); 145 EXPECT_EQ(2, [delegate_ textChangeCount]); 146 147 // Test pressing escape clears the search. First add some text. 148 model->SetText(search_text); 149 EXPECT_EQ(3, [delegate_ textChangeCount]); 150 151 EXPECT_NSEQ(base::SysUTF16ToNSString(search_text), 152 [[apps_search_box_controller_ searchTextField] stringValue]); 153 SimulateKeyAction(@selector(complete:)); 154 EXPECT_NSEQ([NSString string], 155 [[apps_search_box_controller_ searchTextField] stringValue]); 156 EXPECT_EQ(4, [delegate_ textChangeCount]); 157 } 158 159 // Test the popup menu items when there is only one user.. 160 TEST_F(AppsSearchBoxControllerTest, SearchBoxMenuSingleUser) { 161 // Set a single user. We need to set the delegate again because the 162 // AppListModel observer isn't hooked up in these tests. 163 [delegate_ appListTestViewDelegate]->SetUsers( 164 app_list::AppListViewDelegate::Users(1)); 165 [apps_search_box_controller_ setDelegate:delegate_]; 166 167 NSPopUpButton* menu_control = [apps_search_box_controller_ menuControl]; 168 EXPECT_TRUE([apps_search_box_controller_ appListMenu]); 169 ui::MenuModel* menu_model 170 = [apps_search_box_controller_ appListMenu]->menu_model(); 171 // Add one to the item count to account for the blank, first item that Cocoa 172 // has in its popup menus. 173 EXPECT_EQ(menu_model->GetItemCount() + 1, 174 [[menu_control menu] numberOfItems]); 175 176 // All command ids should be less than |SELECT_PROFILE| as no user menu items 177 // are being shown. 178 for (int i = 0; i < menu_model->GetItemCount(); ++i) 179 EXPECT_LT(menu_model->GetCommandIdAt(i), AppListMenu::SELECT_PROFILE); 180 181 // The number of items should match the index that starts profile items. 182 EXPECT_EQ(AppListMenu::SELECT_PROFILE, menu_model->GetItemCount()); 183 } 184 185 // Test the popup menu items for the multi-profile case. 186 TEST_F(AppsSearchBoxControllerTest, SearchBoxMenu) { 187 const app_list::AppListViewDelegate::Users& users = 188 [delegate_ appListDelegate]->GetUsers(); 189 NSPopUpButton* menu_control = [apps_search_box_controller_ menuControl]; 190 EXPECT_TRUE([apps_search_box_controller_ appListMenu]); 191 ui::MenuModel* menu_model 192 = [apps_search_box_controller_ appListMenu]->menu_model(); 193 // Add one to the item count to account for the blank, first item that Cocoa 194 // has in its popup menus. 195 EXPECT_EQ(menu_model->GetItemCount() + 1, 196 [[menu_control menu] numberOfItems]); 197 198 ui::MenuModel* found_menu_model = menu_model; 199 int index; 200 MenuController* controller = [[menu_control menu] delegate]; 201 202 // The first user item is an unchecked label. 203 EXPECT_TRUE(ui::MenuModel::GetModelAndIndexForCommandId( 204 AppListMenu::SELECT_PROFILE, &menu_model, &index)); 205 EXPECT_EQ(found_menu_model, menu_model); 206 NSMenuItem* unchecked_user_item = [[menu_control menu] itemAtIndex:index + 1]; 207 [controller validateUserInterfaceItem:unchecked_user_item]; 208 // The profile name should be shown if there is no email available. 209 EXPECT_NSEQ(base::SysUTF16ToNSString(users[0].name), 210 [unchecked_user_item title]); 211 EXPECT_EQ(NSOffState, [unchecked_user_item state]); 212 213 // The second user item is a checked label because it is the active profile. 214 EXPECT_TRUE(ui::MenuModel::GetModelAndIndexForCommandId( 215 AppListMenu::SELECT_PROFILE + 1, &menu_model, &index)); 216 EXPECT_EQ(found_menu_model, menu_model); 217 NSMenuItem* checked_user_item = [[menu_control menu] itemAtIndex:index + 1]; 218 [controller validateUserInterfaceItem:checked_user_item]; 219 // The email is shown when available. 220 EXPECT_NSEQ(base::SysUTF16ToNSString(users[1].email), 221 [checked_user_item title]); 222 EXPECT_EQ(NSOnState, [checked_user_item state]); 223 224 // A regular item should have just the label. 225 EXPECT_TRUE(ui::MenuModel::GetModelAndIndexForCommandId( 226 AppListMenu::SHOW_SETTINGS, &menu_model, &index)); 227 EXPECT_EQ(found_menu_model, menu_model); 228 NSMenuItem* settings_item = [[menu_control menu] itemAtIndex:index + 1]; 229 EXPECT_FALSE([settings_item view]); 230 EXPECT_NSEQ(base::SysUTF16ToNSString(menu_model->GetLabelAt(index)), 231 [settings_item title]); 232 } 233 234 // Test adding another user, and changing an existing one. 235 TEST_F(AppsSearchBoxControllerTest, SearchBoxMenuChangingUsers) { 236 app_list::AppListViewDelegate::Users users = 237 [delegate_ appListDelegate]->GetUsers(); 238 EXPECT_EQ(2u, users.size()); 239 ui::MenuModel* menu_model 240 = [apps_search_box_controller_ appListMenu]->menu_model(); 241 // Adding one to account for the empty item at index 0 in Cocoa popup menus. 242 int non_user_items = menu_model->GetItemCount() - users.size() + 1; 243 244 NSPopUpButton* menu_control = [apps_search_box_controller_ menuControl]; 245 EXPECT_EQ(2, [[menu_control menu] numberOfItems] - non_user_items); 246 EXPECT_NSEQ(base::SysUTF16ToNSString(users[0].name), 247 [[[menu_control menu] itemAtIndex:1] title]); 248 249 users[0].name = ASCIIToUTF16("renamed user"); 250 app_list::AppListViewDelegate::User new_user; 251 new_user.name = ASCIIToUTF16("user3"); 252 users.push_back(new_user); 253 [delegate_ appListTestViewDelegate]->SetUsers(users); 254 // Note: menu does not automatically get rebuilt. Force a rebuild (which 255 // would normally occur when the UI is closed / re-opend). 256 [apps_search_box_controller_ rebuildMenu]; 257 258 // Should now be an extra item, and it should have correct titles. 259 EXPECT_EQ(3, [[menu_control menu] numberOfItems] - non_user_items); 260 EXPECT_NSEQ(base::SysUTF16ToNSString(users[0].name), 261 [[[menu_control menu] itemAtIndex:1] title]); 262 EXPECT_NSEQ(base::SysUTF16ToNSString(new_user.name), 263 [[[menu_control menu] itemAtIndex:3] title]); 264 } 265 266 } // namespace test 267 } // namespace app_list 268