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