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
19using 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@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
82namespace app_list {
83namespace test {
84
85class 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
122TEST_VIEW(AppsSearchBoxControllerTest, [apps_search_box_controller_ view]);
123
124// Test the search box initialization, and search input and clearing.
125TEST_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..
160TEST_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.
186TEST_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.
235TEST_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