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