apps_search_results_controller_unittest.mm revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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_results_controller.h"
6
7#include "base/mac/scoped_nsobject.h"
8#include "base/strings/stringprintf.h"
9#include "base/strings/sys_string_conversions.h"
10#include "base/strings/utf_string_conversions.h"
11#import "testing/gtest_mac.h"
12#include "ui/app_list/search_result.h"
13#include "ui/app_list/test/app_list_test_model.h"
14#include "ui/base/models/simple_menu_model.h"
15#import "ui/base/test/ui_cocoa_test_helper.h"
16#include "ui/gfx/image/image_skia_util_mac.h"
17#import "ui/base/test/cocoa_test_event_utils.h"
18
19@interface TestAppsSearchResultsDelegate : NSObject<AppsSearchResultsDelegate> {
20 @private
21  app_list::test::AppListTestModel appListModel_;
22  app_list::SearchResult* lastOpenedResult_;
23}
24
25@property(readonly, nonatomic) app_list::SearchResult* lastOpenedResult;
26
27@end
28
29@implementation TestAppsSearchResultsDelegate
30
31@synthesize lastOpenedResult = lastOpenedResult_;
32
33- (app_list::AppListModel*)appListModel {
34  return &appListModel_;
35}
36
37- (void)openResult:(app_list::SearchResult*)result {
38  lastOpenedResult_ = result;
39}
40
41@end
42
43namespace app_list {
44namespace test {
45namespace {
46
47const int kDefaultResultsCount = 3;
48
49class SearchResultWithMenu : public SearchResult {
50 public:
51  SearchResultWithMenu(const std::string& title,
52                       const std::string& details) : menu_model_(NULL) {
53    set_title(ASCIIToUTF16(title));
54    set_details(ASCIIToUTF16(details));
55    menu_model_.AddItem(0, UTF8ToUTF16("Menu For: " + title));
56  }
57
58  virtual ui::MenuModel* GetContextMenuModel() OVERRIDE {
59    return &menu_model_;
60  }
61
62 private:
63  ui::SimpleMenuModel menu_model_;
64
65  DISALLOW_COPY_AND_ASSIGN(SearchResultWithMenu);
66};
67
68class AppsSearchResultsControllerTest : public ui::CocoaTest {
69 public:
70  AppsSearchResultsControllerTest() {}
71
72  void AddTestResultAtIndex(size_t index,
73                            const std::string& title,
74                            const std::string& details) {
75    scoped_ptr<SearchResult> result(new SearchResultWithMenu(title, details));
76    AppListModel::SearchResults* results = [delegate_ appListModel]->results();
77    results->AddAt(index, result.release());
78  }
79
80  SearchResult* ModelResultAt(size_t index) {
81    return [delegate_ appListModel]->results()->GetItemAt(index);
82  }
83
84  NSCell* ViewResultAt(NSInteger index) {
85    NSTableView* table_view = [apps_search_results_controller_ tableView];
86    return [table_view preparedCellAtColumn:0
87                                        row:index];
88  }
89
90  BOOL SimulateKeyAction(SEL c) {
91    return [apps_search_results_controller_ handleCommandBySelector:c];
92  }
93
94  void ExpectConsistent();
95
96  // ui::CocoaTest overrides:
97  virtual void SetUp() OVERRIDE;
98  virtual void TearDown() OVERRIDE;
99
100 protected:
101  base::scoped_nsobject<TestAppsSearchResultsDelegate> delegate_;
102  base::scoped_nsobject<AppsSearchResultsController>
103      apps_search_results_controller_;
104
105 private:
106  DISALLOW_COPY_AND_ASSIGN(AppsSearchResultsControllerTest);
107};
108
109void AppsSearchResultsControllerTest::ExpectConsistent() {
110  NSInteger item_count = [delegate_ appListModel]->results()->item_count();
111  ASSERT_EQ(item_count,
112            [[apps_search_results_controller_ tableView] numberOfRows]);
113
114  // Compare content strings to ensure the order of items is consistent, and any
115  // model data that should have been reloaded has been reloaded in the view.
116  for (NSInteger i = 0; i < item_count; ++i) {
117    SearchResult* result = ModelResultAt(i);
118    base::string16 string_in_model = result->title();
119    if (!result->details().empty())
120      string_in_model += ASCIIToUTF16("\n") + result->details();
121    EXPECT_NSEQ(base::SysUTF16ToNSString(string_in_model),
122                [[ViewResultAt(i) attributedStringValue] string]);
123  }
124}
125
126void AppsSearchResultsControllerTest::SetUp() {
127  apps_search_results_controller_.reset(
128      [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize:
129          NSMakeSize(400, 400)]);
130  // The view is initially hidden. Give it a non-zero height so it draws.
131  [[apps_search_results_controller_ view] setFrameSize:NSMakeSize(400, 400)];
132
133  delegate_.reset([[TestAppsSearchResultsDelegate alloc] init]);
134
135  // Populate with some results so that TEST_VIEW does something non-trivial.
136  for (int i = 0; i < kDefaultResultsCount; ++i)
137    AddTestResultAtIndex(i, base::StringPrintf("Result %d", i), "ItemDetail");
138
139  SearchResult::Tags test_tags;
140  // Apply markup to the substring "Result" in the first item.
141  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::NONE, 0, 1));
142  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::URL, 1, 2));
143  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH, 2, 3));
144  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::DIM, 3, 4));
145  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH |
146                                        SearchResult::Tag::URL, 4, 5));
147  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH |
148                                        SearchResult::Tag::DIM, 5, 6));
149
150  SearchResult* result = ModelResultAt(0);
151  result->SetIcon(gfx::ImageSkiaFromNSImage(
152      [NSImage imageNamed:NSImageNameStatusAvailable]));
153  result->set_title_tags(test_tags);
154
155  [apps_search_results_controller_ setDelegate:delegate_];
156
157  ui::CocoaTest::SetUp();
158  [[test_window() contentView] addSubview:
159      [apps_search_results_controller_ view]];
160}
161
162void AppsSearchResultsControllerTest::TearDown() {
163  [apps_search_results_controller_ setDelegate:nil];
164  ui::CocoaTest::TearDown();
165}
166
167NSEvent* MouseEventInRow(NSTableView* table_view, NSInteger row_index) {
168  NSRect row_rect = [table_view rectOfRow:row_index];
169  NSPoint point_in_view = NSMakePoint(NSMidX(row_rect), NSMidY(row_rect));
170  NSPoint point_in_window = [table_view convertPoint:point_in_view
171                                              toView:nil];
172  return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
173}
174
175}  // namespace
176
177TEST_VIEW(AppsSearchResultsControllerTest,
178          [apps_search_results_controller_ view]);
179
180TEST_F(AppsSearchResultsControllerTest, ModelObservers) {
181  NSTableView* table_view = [apps_search_results_controller_ tableView];
182  ExpectConsistent();
183
184  EXPECT_EQ(1, [table_view numberOfColumns]);
185  EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
186
187  // Insert at start.
188  AddTestResultAtIndex(0, "One", std::string());
189  EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
190  ExpectConsistent();
191
192  // Remove from end.
193  [delegate_ appListModel]->results()->DeleteAt(kDefaultResultsCount);
194  EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
195  ExpectConsistent();
196
197  // Insert at end.
198  AddTestResultAtIndex(kDefaultResultsCount, "Four", std::string());
199  EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
200  ExpectConsistent();
201
202  // Delete from start.
203  [delegate_ appListModel]->results()->DeleteAt(0);
204  EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
205  ExpectConsistent();
206
207  // Test clearing results.
208  [delegate_ appListModel]->results()->DeleteAll();
209  EXPECT_EQ(0, [table_view numberOfRows]);
210  ExpectConsistent();
211}
212
213TEST_F(AppsSearchResultsControllerTest, KeyboardSelectAndActivate) {
214  NSTableView* table_view = [apps_search_results_controller_ tableView];
215  EXPECT_EQ(-1, [table_view selectedRow]);
216
217  // Pressing up when nothing is selected should select the last item.
218  EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
219  EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);
220  [table_view deselectAll:nil];
221  EXPECT_EQ(-1, [table_view selectedRow]);
222
223  // Pressing down when nothing is selected should select the first item.
224  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
225  EXPECT_EQ(0, [table_view selectedRow]);
226
227  // Pressing up should wrap around.
228  EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
229  EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);
230
231  // Down should now also wrap, since the selection is at the end.
232  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
233  EXPECT_EQ(0, [table_view selectedRow]);
234
235  // Regular down and up movement, ensuring the cells have correct backgrounds.
236  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
237  EXPECT_EQ(1, [table_view selectedRow]);
238  EXPECT_EQ(NSBackgroundStyleDark, [ViewResultAt(1) backgroundStyle]);
239  EXPECT_EQ(NSBackgroundStyleLight, [ViewResultAt(0) backgroundStyle]);
240
241  EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
242  EXPECT_EQ(0, [table_view selectedRow]);
243  EXPECT_EQ(NSBackgroundStyleDark, [ViewResultAt(0) backgroundStyle]);
244  EXPECT_EQ(NSBackgroundStyleLight, [ViewResultAt(1) backgroundStyle]);
245
246  // Test activating items.
247  EXPECT_TRUE(SimulateKeyAction(@selector(insertNewline:)));
248  EXPECT_EQ(ModelResultAt(0), [delegate_ lastOpenedResult]);
249  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
250  EXPECT_TRUE(SimulateKeyAction(@selector(insertNewline:)));
251  EXPECT_EQ(ModelResultAt(1), [delegate_ lastOpenedResult]);
252}
253
254TEST_F(AppsSearchResultsControllerTest, ContextMenus) {
255  NSTableView* table_view = [apps_search_results_controller_ tableView];
256  NSEvent* mouse_in_row_0 = MouseEventInRow(table_view, 0);
257  NSEvent* mouse_in_row_1 = MouseEventInRow(table_view, 1);
258
259  NSMenu* menu = [table_view menuForEvent:mouse_in_row_0];
260  EXPECT_EQ(1, [menu numberOfItems]);
261  EXPECT_NSEQ(@"Menu For: Result 0", [[menu itemAtIndex:0] title]);
262
263  menu = [table_view menuForEvent:mouse_in_row_1];
264  EXPECT_EQ(1, [menu numberOfItems]);
265  EXPECT_NSEQ(@"Menu For: Result 1", [[menu itemAtIndex:0] title]);
266}
267
268}  // namespace test
269}  // namespace app_list
270