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/message_loop/message_loop.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #import "testing/gtest_mac.h"
13 #include "ui/app_list/test/app_list_test_model.h"
14 #include "ui/app_list/test/test_search_result.h"
15 #include "ui/base/models/simple_menu_model.h"
16 #import "ui/events/test/cocoa_test_event_utils.h"
17 #include "ui/gfx/image/image_skia_util_mac.h"
18 #import "ui/gfx/test/ui_cocoa_test_helper.h"
20 @interface TestAppsSearchResultsDelegate : NSObject<AppsSearchResultsDelegate> {
22 app_list::test::AppListTestModel appListModel_;
23 app_list::SearchResult* lastOpenedResult_;
26 @property(readonly, nonatomic) app_list::SearchResult* lastOpenedResult;
28 - (void)quitMessageLoop;
32 @implementation TestAppsSearchResultsDelegate
34 @synthesize lastOpenedResult = lastOpenedResult_;
36 - (app_list::AppListModel*)appListModel {
37 return &appListModel_;
40 - (void)openResult:(app_list::SearchResult*)result {
41 lastOpenedResult_ = result;
44 - (void)quitMessageLoop {
45 base::MessageLoop::current()->QuitNow();
54 const int kDefaultResultsCount = 3;
56 class SearchResultWithMenu : public TestSearchResult {
58 SearchResultWithMenu(const std::string& title, const std::string& details)
61 set_title(base::ASCIIToUTF16(title));
62 set_details(base::ASCIIToUTF16(details));
63 menu_model_.AddItem(0, base::UTF8ToUTF16("Menu For: " + title));
66 void SetMenuReadyForTesting(bool ready) {
70 ui::MenuModel* GetContextMenuModel() override {
78 ui::SimpleMenuModel menu_model_;
81 DISALLOW_COPY_AND_ASSIGN(SearchResultWithMenu);
84 class AppsSearchResultsControllerTest : public ui::CocoaTest {
86 AppsSearchResultsControllerTest() {}
88 void AddTestResultAtIndex(size_t index,
89 const std::string& title,
90 const std::string& details) {
91 scoped_ptr<SearchResult> result(new SearchResultWithMenu(title, details));
92 AppListModel::SearchResults* results = [delegate_ appListModel]->results();
93 results->AddAt(index, result.release());
96 SearchResult* ModelResultAt(size_t index) {
97 return [delegate_ appListModel]->results()->GetItemAt(index);
100 NSCell* ViewResultAt(NSInteger index) {
101 NSTableView* table_view = [apps_search_results_controller_ tableView];
102 return [table_view preparedCellAtColumn:0
106 void SetMenuReadyAt(size_t index, bool ready) {
107 SearchResultWithMenu* result =
108 static_cast<SearchResultWithMenu*>(ModelResultAt(index));
109 result->SetMenuReadyForTesting(ready);
112 BOOL SimulateKeyAction(SEL c) {
113 return [apps_search_results_controller_ handleCommandBySelector:c];
116 void ExpectConsistent();
118 // ui::CocoaTest overrides:
119 void SetUp() override;
120 void TearDown() override;
123 base::scoped_nsobject<TestAppsSearchResultsDelegate> delegate_;
124 base::scoped_nsobject<AppsSearchResultsController>
125 apps_search_results_controller_;
128 DISALLOW_COPY_AND_ASSIGN(AppsSearchResultsControllerTest);
131 void AppsSearchResultsControllerTest::ExpectConsistent() {
132 NSInteger item_count = [delegate_ appListModel]->results()->item_count();
133 ASSERT_EQ(item_count,
134 [[apps_search_results_controller_ tableView] numberOfRows]);
136 // Compare content strings to ensure the order of items is consistent, and any
137 // model data that should have been reloaded has been reloaded in the view.
138 for (NSInteger i = 0; i < item_count; ++i) {
139 SearchResult* result = ModelResultAt(i);
140 base::string16 string_in_model = result->title();
141 if (!result->details().empty())
142 string_in_model += base::ASCIIToUTF16("\n") + result->details();
143 EXPECT_NSEQ(base::SysUTF16ToNSString(string_in_model),
144 [[ViewResultAt(i) attributedStringValue] string]);
148 void AppsSearchResultsControllerTest::SetUp() {
149 apps_search_results_controller_.reset(
150 [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize:
151 NSMakeSize(400, 400)]);
152 // The view is initially hidden. Give it a non-zero height so it draws.
153 [[apps_search_results_controller_ view] setFrameSize:NSMakeSize(400, 400)];
155 delegate_.reset([[TestAppsSearchResultsDelegate alloc] init]);
157 // Populate with some results so that TEST_VIEW does something non-trivial.
158 for (int i = 0; i < kDefaultResultsCount; ++i)
159 AddTestResultAtIndex(i, base::StringPrintf("Result %d", i), "ItemDetail");
161 SearchResult::Tags test_tags;
162 // Apply markup to the substring "Result" in the first item.
163 test_tags.push_back(SearchResult::Tag(SearchResult::Tag::NONE, 0, 1));
164 test_tags.push_back(SearchResult::Tag(SearchResult::Tag::URL, 1, 2));
165 test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH, 2, 3));
166 test_tags.push_back(SearchResult::Tag(SearchResult::Tag::DIM, 3, 4));
167 test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH |
168 SearchResult::Tag::URL, 4, 5));
169 test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH |
170 SearchResult::Tag::DIM, 5, 6));
172 SearchResult* result = ModelResultAt(0);
173 result->SetIcon(gfx::ImageSkiaFromNSImage(
174 [NSImage imageNamed:NSImageNameStatusAvailable]));
175 result->set_title_tags(test_tags);
177 [apps_search_results_controller_ setDelegate:delegate_];
179 ui::CocoaTest::SetUp();
180 [[test_window() contentView] addSubview:
181 [apps_search_results_controller_ view]];
184 void AppsSearchResultsControllerTest::TearDown() {
185 [apps_search_results_controller_ setDelegate:nil];
186 ui::CocoaTest::TearDown();
189 NSEvent* MouseEventInRow(NSTableView* table_view, NSInteger row_index) {
190 NSRect row_rect = [table_view rectOfRow:row_index];
191 NSPoint point_in_view = NSMakePoint(NSMidX(row_rect), NSMidY(row_rect));
192 NSPoint point_in_window = [table_view convertPoint:point_in_view
194 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
199 TEST_VIEW(AppsSearchResultsControllerTest,
200 [apps_search_results_controller_ view]);
202 TEST_F(AppsSearchResultsControllerTest, ModelObservers) {
203 NSTableView* table_view = [apps_search_results_controller_ tableView];
206 EXPECT_EQ(1, [table_view numberOfColumns]);
207 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
210 AddTestResultAtIndex(0, "One", std::string());
211 EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
215 [delegate_ appListModel]->results()->DeleteAt(kDefaultResultsCount);
216 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
220 AddTestResultAtIndex(kDefaultResultsCount, "Four", std::string());
221 EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
224 // Delete from start.
225 [delegate_ appListModel]->results()->DeleteAt(0);
226 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
229 // Test clearing results.
230 [delegate_ appListModel]->results()->DeleteAll();
231 EXPECT_EQ(0, [table_view numberOfRows]);
235 TEST_F(AppsSearchResultsControllerTest, KeyboardSelectAndActivate) {
236 NSTableView* table_view = [apps_search_results_controller_ tableView];
237 EXPECT_EQ(-1, [table_view selectedRow]);
239 // Pressing up when nothing is selected should select the last item.
240 EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
241 EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);
242 [table_view deselectAll:nil];
243 EXPECT_EQ(-1, [table_view selectedRow]);
245 // Pressing down when nothing is selected should select the first item.
246 EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
247 EXPECT_EQ(0, [table_view selectedRow]);
249 // Pressing up should wrap around.
250 EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
251 EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);
253 // Down should now also wrap, since the selection is at the end.
254 EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
255 EXPECT_EQ(0, [table_view selectedRow]);
257 // Regular down and up movement, ensuring the cells have correct backgrounds.
258 EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
259 EXPECT_EQ(1, [table_view selectedRow]);
260 EXPECT_EQ(NSBackgroundStyleDark, [ViewResultAt(1) backgroundStyle]);
261 EXPECT_EQ(NSBackgroundStyleLight, [ViewResultAt(0) backgroundStyle]);
263 EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
264 EXPECT_EQ(0, [table_view selectedRow]);
265 EXPECT_EQ(NSBackgroundStyleDark, [ViewResultAt(0) backgroundStyle]);
266 EXPECT_EQ(NSBackgroundStyleLight, [ViewResultAt(1) backgroundStyle]);
268 // Test activating items.
269 EXPECT_TRUE(SimulateKeyAction(@selector(insertNewline:)));
270 EXPECT_EQ(ModelResultAt(0), [delegate_ lastOpenedResult]);
271 EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
272 EXPECT_TRUE(SimulateKeyAction(@selector(insertNewline:)));
273 EXPECT_EQ(ModelResultAt(1), [delegate_ lastOpenedResult]);
276 TEST_F(AppsSearchResultsControllerTest, ContextMenus) {
277 NSTableView* table_view = [apps_search_results_controller_ tableView];
278 NSEvent* mouse_in_row_0 = MouseEventInRow(table_view, 0);
279 NSEvent* mouse_in_row_1 = MouseEventInRow(table_view, 1);
281 NSMenu* menu = [table_view menuForEvent:mouse_in_row_0];
282 EXPECT_EQ(1, [menu numberOfItems]);
283 EXPECT_NSEQ(@"Menu For: Result 0", [[menu itemAtIndex:0] title]);
285 // Test a context menu request while the item is still installing.
286 SetMenuReadyAt(1, false);
287 menu = [table_view menuForEvent:mouse_in_row_1];
288 EXPECT_EQ(nil, menu);
290 SetMenuReadyAt(1, true);
291 menu = [table_view menuForEvent:mouse_in_row_1];
292 EXPECT_EQ(1, [menu numberOfItems]);
293 EXPECT_NSEQ(@"Menu For: Result 1", [[menu itemAtIndex:0] title]);
297 } // namespace app_list