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.
5 #import "ui/app_list/cocoa/apps_search_results_controller.h"
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"
19 @interface TestAppsSearchResultsDelegate : NSObject<AppsSearchResultsDelegate> {
21 app_list::test::AppListTestModel appListModel_;
22 app_list::SearchResult* lastOpenedResult_;
25 @property(readonly, nonatomic) app_list::SearchResult* lastOpenedResult;
29 @implementation TestAppsSearchResultsDelegate
31 @synthesize lastOpenedResult = lastOpenedResult_;
33 - (app_list::AppListModel*)appListModel {
34 return &appListModel_;
37 - (void)openResult:(app_list::SearchResult*)result {
38 lastOpenedResult_ = result;
47 const int kDefaultResultsCount = 3;
49 class SearchResultWithMenu : public SearchResult {
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));
58 virtual ui::MenuModel* GetContextMenuModel() OVERRIDE {
63 ui::SimpleMenuModel menu_model_;
65 DISALLOW_COPY_AND_ASSIGN(SearchResultWithMenu);
68 class AppsSearchResultsControllerTest : public ui::CocoaTest {
70 AppsSearchResultsControllerTest() {}
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());
80 SearchResult* ModelResultAt(size_t index) {
81 return [delegate_ appListModel]->results()->GetItemAt(index);
84 NSCell* ViewResultAt(NSInteger index) {
85 NSTableView* table_view = [apps_search_results_controller_ tableView];
86 return [table_view preparedCellAtColumn:0
90 BOOL SimulateKeyAction(SEL c) {
91 return [apps_search_results_controller_ handleCommandBySelector:c];
94 void ExpectConsistent();
96 // ui::CocoaTest overrides:
97 virtual void SetUp() OVERRIDE;
98 virtual void TearDown() OVERRIDE;
101 base::scoped_nsobject<TestAppsSearchResultsDelegate> delegate_;
102 base::scoped_nsobject<AppsSearchResultsController>
103 apps_search_results_controller_;
106 DISALLOW_COPY_AND_ASSIGN(AppsSearchResultsControllerTest);
109 void AppsSearchResultsControllerTest::ExpectConsistent() {
110 NSInteger item_count = [delegate_ appListModel]->results()->item_count();
111 ASSERT_EQ(item_count,
112 [[apps_search_results_controller_ tableView] numberOfRows]);
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]);
126 void 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)];
133 delegate_.reset([[TestAppsSearchResultsDelegate alloc] init]);
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");
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));
150 SearchResult* result = ModelResultAt(0);
151 result->SetIcon(gfx::ImageSkiaFromNSImage(
152 [NSImage imageNamed:NSImageNameStatusAvailable]));
153 result->set_title_tags(test_tags);
155 [apps_search_results_controller_ setDelegate:delegate_];
157 ui::CocoaTest::SetUp();
158 [[test_window() contentView] addSubview:
159 [apps_search_results_controller_ view]];
162 void AppsSearchResultsControllerTest::TearDown() {
163 [apps_search_results_controller_ setDelegate:nil];
164 ui::CocoaTest::TearDown();
167 NSEvent* 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
172 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
177 TEST_VIEW(AppsSearchResultsControllerTest,
178 [apps_search_results_controller_ view]);
180 TEST_F(AppsSearchResultsControllerTest, ModelObservers) {
181 NSTableView* table_view = [apps_search_results_controller_ tableView];
184 EXPECT_EQ(1, [table_view numberOfColumns]);
185 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
188 AddTestResultAtIndex(0, "One", std::string());
189 EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
193 [delegate_ appListModel]->results()->DeleteAt(kDefaultResultsCount);
194 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
198 AddTestResultAtIndex(kDefaultResultsCount, "Four", std::string());
199 EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
202 // Delete from start.
203 [delegate_ appListModel]->results()->DeleteAt(0);
204 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
207 // Test clearing results.
208 [delegate_ appListModel]->results()->DeleteAll();
209 EXPECT_EQ(0, [table_view numberOfRows]);
213 TEST_F(AppsSearchResultsControllerTest, KeyboardSelectAndActivate) {
214 NSTableView* table_view = [apps_search_results_controller_ tableView];
215 EXPECT_EQ(-1, [table_view selectedRow]);
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]);
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]);
227 // Pressing up should wrap around.
228 EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
229 EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);
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]);
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]);
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]);
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]);
254 TEST_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);
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]);
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]);
269 } // namespace app_list