apps_search_results_controller_unittest.mm revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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/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" 18 19@interface TestAppsSearchResultsDelegate : NSObject<AppsSearchResultsDelegate> { 20 @private 21 app_list::test::AppListTestModel appListModel_; 22 app_list::SearchResult* lastOpenedResult_; 23} 24 25@property(readonly, nonatomic) app_list::SearchResult* lastOpenedResult; 26 27@end 28 29@implementation TestAppsSearchResultsDelegate 30 31@synthesize lastOpenedResult = lastOpenedResult_; 32 33- (app_list::AppListModel*)appListModel { 34 return &appListModel_; 35} 36 37- (void)openResult:(app_list::SearchResult*)result { 38 lastOpenedResult_ = result; 39} 40 41@end 42 43namespace app_list { 44namespace test { 45namespace { 46 47const int kDefaultResultsCount = 3; 48 49class SearchResultWithMenu : public SearchResult { 50 public: 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)); 56 } 57 58 virtual ui::MenuModel* GetContextMenuModel() OVERRIDE { 59 return &menu_model_; 60 } 61 62 private: 63 ui::SimpleMenuModel menu_model_; 64 65 DISALLOW_COPY_AND_ASSIGN(SearchResultWithMenu); 66}; 67 68class AppsSearchResultsControllerTest : public ui::CocoaTest { 69 public: 70 AppsSearchResultsControllerTest() {} 71 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()); 78 } 79 80 SearchResult* ModelResultAt(size_t index) { 81 return [delegate_ appListModel]->results()->GetItemAt(index); 82 } 83 84 NSCell* ViewResultAt(NSInteger index) { 85 NSTableView* table_view = [apps_search_results_controller_ tableView]; 86 return [table_view preparedCellAtColumn:0 87 row:index]; 88 } 89 90 BOOL SimulateKeyAction(SEL c) { 91 return [apps_search_results_controller_ handleCommandBySelector:c]; 92 } 93 94 void ExpectConsistent(); 95 96 // ui::CocoaTest overrides: 97 virtual void SetUp() OVERRIDE; 98 virtual void TearDown() OVERRIDE; 99 100 protected: 101 base::scoped_nsobject<TestAppsSearchResultsDelegate> delegate_; 102 base::scoped_nsobject<AppsSearchResultsController> 103 apps_search_results_controller_; 104 105 private: 106 DISALLOW_COPY_AND_ASSIGN(AppsSearchResultsControllerTest); 107}; 108 109void AppsSearchResultsControllerTest::ExpectConsistent() { 110 NSInteger item_count = [delegate_ appListModel]->results()->item_count(); 111 ASSERT_EQ(item_count, 112 [[apps_search_results_controller_ tableView] numberOfRows]); 113 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]); 123 } 124} 125 126void 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)]; 132 133 delegate_.reset([[TestAppsSearchResultsDelegate alloc] init]); 134 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"); 138 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)); 149 150 SearchResult* result = ModelResultAt(0); 151 result->SetIcon(gfx::ImageSkiaFromNSImage( 152 [NSImage imageNamed:NSImageNameStatusAvailable])); 153 result->set_title_tags(test_tags); 154 155 [apps_search_results_controller_ setDelegate:delegate_]; 156 157 ui::CocoaTest::SetUp(); 158 [[test_window() contentView] addSubview: 159 [apps_search_results_controller_ view]]; 160} 161 162void AppsSearchResultsControllerTest::TearDown() { 163 [apps_search_results_controller_ setDelegate:nil]; 164 ui::CocoaTest::TearDown(); 165} 166 167NSEvent* 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 171 toView:nil]; 172 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window); 173} 174 175} // namespace 176 177TEST_VIEW(AppsSearchResultsControllerTest, 178 [apps_search_results_controller_ view]); 179 180TEST_F(AppsSearchResultsControllerTest, ModelObservers) { 181 NSTableView* table_view = [apps_search_results_controller_ tableView]; 182 ExpectConsistent(); 183 184 EXPECT_EQ(1, [table_view numberOfColumns]); 185 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]); 186 187 // Insert at start. 188 AddTestResultAtIndex(0, "One", std::string()); 189 EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]); 190 ExpectConsistent(); 191 192 // Remove from end. 193 [delegate_ appListModel]->results()->DeleteAt(kDefaultResultsCount); 194 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]); 195 ExpectConsistent(); 196 197 // Insert at end. 198 AddTestResultAtIndex(kDefaultResultsCount, "Four", std::string()); 199 EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]); 200 ExpectConsistent(); 201 202 // Delete from start. 203 [delegate_ appListModel]->results()->DeleteAt(0); 204 EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]); 205 ExpectConsistent(); 206 207 // Test clearing results. 208 [delegate_ appListModel]->results()->DeleteAll(); 209 EXPECT_EQ(0, [table_view numberOfRows]); 210 ExpectConsistent(); 211} 212 213TEST_F(AppsSearchResultsControllerTest, KeyboardSelectAndActivate) { 214 NSTableView* table_view = [apps_search_results_controller_ tableView]; 215 EXPECT_EQ(-1, [table_view selectedRow]); 216 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]); 222 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]); 226 227 // Pressing up should wrap around. 228 EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:))); 229 EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]); 230 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]); 234 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]); 240 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]); 245 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]); 252} 253 254TEST_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); 258 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]); 262 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]); 266} 267 268} // namespace test 269} // namespace app_list 270