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