Views Omnibox: tolerate minor click-to-select-all dragging.
[chromium-blink-merge.git] / ui / app_list / cocoa / apps_search_results_controller_unittest.mm
blob72253c04ef9461e5e289dacef057626fa18f65d0
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/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 #import "ui/events/test/cocoa_test_event_utils.h"
18 #include "ui/gfx/image/image_skia_util_mac.h"
20 @interface TestAppsSearchResultsDelegate : NSObject<AppsSearchResultsDelegate> {
21  @private
22   app_list::test::AppListTestModel appListModel_;
23   app_list::SearchResult* lastOpenedResult_;
24   int redoSearchCount_;
27 @property(readonly, nonatomic) app_list::SearchResult* lastOpenedResult;
28 @property(readonly, nonatomic) int redoSearchCount;
30 - (void)quitMessageLoop;
32 @end
34 @implementation TestAppsSearchResultsDelegate
36 @synthesize lastOpenedResult = lastOpenedResult_;
37 @synthesize redoSearchCount = redoSearchCount_;
39 - (app_list::AppListModel*)appListModel {
40   return &appListModel_;
43 - (void)openResult:(app_list::SearchResult*)result {
44   lastOpenedResult_ = result;
47 - (void)redoSearch {
48   ++redoSearchCount_;
51 - (void)quitMessageLoop {
52   base::MessageLoop::current()->QuitNow();
55 @end
57 namespace app_list {
58 namespace test {
59 namespace {
61 const int kDefaultResultsCount = 3;
63 class 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(base::ASCIIToUTF16(title));
69     set_details(base::ASCIIToUTF16(details));
70     menu_model_.AddItem(0, base::UTF8ToUTF16("Menu For: " + title));
71   }
73   void SetMenuReadyForTesting(bool ready) {
74     menu_ready_ = ready;
75   }
77   virtual ui::MenuModel* GetContextMenuModel() OVERRIDE {
78     if (!menu_ready_)
79       return NULL;
81     return &menu_model_;
82   }
84  private:
85   ui::SimpleMenuModel menu_model_;
86   bool menu_ready_;
88   DISALLOW_COPY_AND_ASSIGN(SearchResultWithMenu);
91 class AppsSearchResultsControllerTest : public ui::CocoaTest {
92  public:
93   AppsSearchResultsControllerTest() {}
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   }
103   SearchResult* ModelResultAt(size_t index) {
104     return [delegate_ appListModel]->results()->GetItemAt(index);
105   }
107   NSCell* ViewResultAt(NSInteger index) {
108     NSTableView* table_view = [apps_search_results_controller_ tableView];
109     return [table_view preparedCellAtColumn:0
110                                         row:index];
111   }
113   void SetMenuReadyAt(size_t index, bool ready) {
114     SearchResultWithMenu* result =
115         static_cast<SearchResultWithMenu*>(ModelResultAt(index));
116     result->SetMenuReadyForTesting(ready);
117   }
119   BOOL SimulateKeyAction(SEL c) {
120     return [apps_search_results_controller_ handleCommandBySelector:c];
121   }
123   void ExpectConsistent();
125   // ui::CocoaTest overrides:
126   virtual void SetUp() OVERRIDE;
127   virtual void TearDown() OVERRIDE;
129  protected:
130   base::scoped_nsobject<TestAppsSearchResultsDelegate> delegate_;
131   base::scoped_nsobject<AppsSearchResultsController>
132       apps_search_results_controller_;
134  private:
135   DISALLOW_COPY_AND_ASSIGN(AppsSearchResultsControllerTest);
138 void AppsSearchResultsControllerTest::ExpectConsistent() {
139   NSInteger item_count = [delegate_ appListModel]->results()->item_count();
140   ASSERT_EQ(item_count,
141             [[apps_search_results_controller_ tableView] numberOfRows]);
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 += base::ASCIIToUTF16("\n") + result->details();
150     EXPECT_NSEQ(base::SysUTF16ToNSString(string_in_model),
151                 [[ViewResultAt(i) attributedStringValue] string]);
152   }
155 void 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)];
162   delegate_.reset([[TestAppsSearchResultsDelegate alloc] init]);
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");
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));
179   SearchResult* result = ModelResultAt(0);
180   result->SetIcon(gfx::ImageSkiaFromNSImage(
181       [NSImage imageNamed:NSImageNameStatusAvailable]));
182   result->set_title_tags(test_tags);
184   [apps_search_results_controller_ setDelegate:delegate_];
186   ui::CocoaTest::SetUp();
187   [[test_window() contentView] addSubview:
188       [apps_search_results_controller_ view]];
191 void AppsSearchResultsControllerTest::TearDown() {
192   [apps_search_results_controller_ setDelegate:nil];
193   ui::CocoaTest::TearDown();
196 NSEvent* 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);
204 }  // namespace
206 TEST_VIEW(AppsSearchResultsControllerTest,
207           [apps_search_results_controller_ view]);
209 TEST_F(AppsSearchResultsControllerTest, ModelObservers) {
210   NSTableView* table_view = [apps_search_results_controller_ tableView];
211   ExpectConsistent();
213   EXPECT_EQ(1, [table_view numberOfColumns]);
214   EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
216   // Insert at start.
217   AddTestResultAtIndex(0, "One", std::string());
218   EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
219   ExpectConsistent();
221   // Remove from end.
222   [delegate_ appListModel]->results()->DeleteAt(kDefaultResultsCount);
223   EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
224   ExpectConsistent();
226   // Insert at end.
227   AddTestResultAtIndex(kDefaultResultsCount, "Four", std::string());
228   EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
229   ExpectConsistent();
231   // Delete from start.
232   [delegate_ appListModel]->results()->DeleteAt(0);
233   EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
234   ExpectConsistent();
236   // Test clearing results.
237   [delegate_ appListModel]->results()->DeleteAll();
238   EXPECT_EQ(0, [table_view numberOfRows]);
239   ExpectConsistent();
242 TEST_F(AppsSearchResultsControllerTest, KeyboardSelectAndActivate) {
243   NSTableView* table_view = [apps_search_results_controller_ tableView];
244   EXPECT_EQ(-1, [table_view selectedRow]);
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]);
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]);
256   // Pressing up should wrap around.
257   EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
258   EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);
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]);
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]);
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]);
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]);
283 TEST_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);
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]);
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);
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]);
303 // Test that observing a search result item uninstall performs the search again.
304 TEST_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]);
315 }  // namespace test
316 }  // namespace app_list