summaryrefslogtreecommitdiff
path: root/chromium/ui/app_list/cocoa/apps_search_results_controller_unittest.mm
blob: 3645e1e19debe5beffd452fc25b7ff94917ff4a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ui/app_list/cocoa/apps_search_results_controller.h"

#include "base/mac/scoped_nsobject.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "testing/gtest_mac.h"
#include "ui/app_list/search_result.h"
#include "ui/app_list/test/app_list_test_model.h"
#include "ui/base/models/simple_menu_model.h"
#import "ui/base/test/ui_cocoa_test_helper.h"
#include "ui/gfx/image/image_skia_util_mac.h"
#import "ui/base/test/cocoa_test_event_utils.h"

@interface TestAppsSearchResultsDelegate : NSObject<AppsSearchResultsDelegate> {
 @private
  app_list::test::AppListTestModel appListModel_;
  app_list::SearchResult* lastOpenedResult_;
  int redoSearchCount_;
}

@property(readonly, nonatomic) app_list::SearchResult* lastOpenedResult;
@property(readonly, nonatomic) int redoSearchCount;

@end

@implementation TestAppsSearchResultsDelegate

@synthesize lastOpenedResult = lastOpenedResult_;
@synthesize redoSearchCount = redoSearchCount_;

- (app_list::AppListModel*)appListModel {
  return &appListModel_;
}

- (void)openResult:(app_list::SearchResult*)result {
  lastOpenedResult_ = result;
}

- (void)redoSearch {
  ++redoSearchCount_;
}

@end

namespace app_list {
namespace test {
namespace {

const int kDefaultResultsCount = 3;

class SearchResultWithMenu : public SearchResult {
 public:
  SearchResultWithMenu(const std::string& title, const std::string& details)
      : menu_model_(NULL),
        menu_ready_(true) {
    set_title(ASCIIToUTF16(title));
    set_details(ASCIIToUTF16(details));
    menu_model_.AddItem(0, UTF8ToUTF16("Menu For: " + title));
  }

  void SetMenuReadyForTesting(bool ready) {
    menu_ready_ = ready;
  }

  virtual ui::MenuModel* GetContextMenuModel() OVERRIDE {
    if (!menu_ready_)
      return NULL;

    return &menu_model_;
  }

 private:
  ui::SimpleMenuModel menu_model_;
  bool menu_ready_;

  DISALLOW_COPY_AND_ASSIGN(SearchResultWithMenu);
};

class AppsSearchResultsControllerTest : public ui::CocoaTest {
 public:
  AppsSearchResultsControllerTest() {}

  void AddTestResultAtIndex(size_t index,
                            const std::string& title,
                            const std::string& details) {
    scoped_ptr<SearchResult> result(new SearchResultWithMenu(title, details));
    AppListModel::SearchResults* results = [delegate_ appListModel]->results();
    results->AddAt(index, result.release());
  }

  SearchResult* ModelResultAt(size_t index) {
    return [delegate_ appListModel]->results()->GetItemAt(index);
  }

  NSCell* ViewResultAt(NSInteger index) {
    NSTableView* table_view = [apps_search_results_controller_ tableView];
    return [table_view preparedCellAtColumn:0
                                        row:index];
  }

  void SetMenuReadyAt(size_t index, bool ready) {
    SearchResultWithMenu* result =
        static_cast<SearchResultWithMenu*>(ModelResultAt(index));
    result->SetMenuReadyForTesting(ready);
  }

  BOOL SimulateKeyAction(SEL c) {
    return [apps_search_results_controller_ handleCommandBySelector:c];
  }

  void ExpectConsistent();

  // ui::CocoaTest overrides:
  virtual void SetUp() OVERRIDE;
  virtual void TearDown() OVERRIDE;

 protected:
  base::scoped_nsobject<TestAppsSearchResultsDelegate> delegate_;
  base::scoped_nsobject<AppsSearchResultsController>
      apps_search_results_controller_;

 private:
  DISALLOW_COPY_AND_ASSIGN(AppsSearchResultsControllerTest);
};

void AppsSearchResultsControllerTest::ExpectConsistent() {
  NSInteger item_count = [delegate_ appListModel]->results()->item_count();
  ASSERT_EQ(item_count,
            [[apps_search_results_controller_ tableView] numberOfRows]);

  // Compare content strings to ensure the order of items is consistent, and any
  // model data that should have been reloaded has been reloaded in the view.
  for (NSInteger i = 0; i < item_count; ++i) {
    SearchResult* result = ModelResultAt(i);
    base::string16 string_in_model = result->title();
    if (!result->details().empty())
      string_in_model += ASCIIToUTF16("\n") + result->details();
    EXPECT_NSEQ(base::SysUTF16ToNSString(string_in_model),
                [[ViewResultAt(i) attributedStringValue] string]);
  }
}

void AppsSearchResultsControllerTest::SetUp() {
  apps_search_results_controller_.reset(
      [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize:
          NSMakeSize(400, 400)]);
  // The view is initially hidden. Give it a non-zero height so it draws.
  [[apps_search_results_controller_ view] setFrameSize:NSMakeSize(400, 400)];

  delegate_.reset([[TestAppsSearchResultsDelegate alloc] init]);

  // Populate with some results so that TEST_VIEW does something non-trivial.
  for (int i = 0; i < kDefaultResultsCount; ++i)
    AddTestResultAtIndex(i, base::StringPrintf("Result %d", i), "ItemDetail");

  SearchResult::Tags test_tags;
  // Apply markup to the substring "Result" in the first item.
  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::NONE, 0, 1));
  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::URL, 1, 2));
  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH, 2, 3));
  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::DIM, 3, 4));
  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH |
                                        SearchResult::Tag::URL, 4, 5));
  test_tags.push_back(SearchResult::Tag(SearchResult::Tag::MATCH |
                                        SearchResult::Tag::DIM, 5, 6));

  SearchResult* result = ModelResultAt(0);
  result->SetIcon(gfx::ImageSkiaFromNSImage(
      [NSImage imageNamed:NSImageNameStatusAvailable]));
  result->set_title_tags(test_tags);

  [apps_search_results_controller_ setDelegate:delegate_];

  ui::CocoaTest::SetUp();
  [[test_window() contentView] addSubview:
      [apps_search_results_controller_ view]];
}

void AppsSearchResultsControllerTest::TearDown() {
  [apps_search_results_controller_ setDelegate:nil];
  ui::CocoaTest::TearDown();
}

NSEvent* MouseEventInRow(NSTableView* table_view, NSInteger row_index) {
  NSRect row_rect = [table_view rectOfRow:row_index];
  NSPoint point_in_view = NSMakePoint(NSMidX(row_rect), NSMidY(row_rect));
  NSPoint point_in_window = [table_view convertPoint:point_in_view
                                              toView:nil];
  return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
}

}  // namespace

TEST_VIEW(AppsSearchResultsControllerTest,
          [apps_search_results_controller_ view]);

TEST_F(AppsSearchResultsControllerTest, ModelObservers) {
  NSTableView* table_view = [apps_search_results_controller_ tableView];
  ExpectConsistent();

  EXPECT_EQ(1, [table_view numberOfColumns]);
  EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);

  // Insert at start.
  AddTestResultAtIndex(0, "One", std::string());
  EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
  ExpectConsistent();

  // Remove from end.
  [delegate_ appListModel]->results()->DeleteAt(kDefaultResultsCount);
  EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
  ExpectConsistent();

  // Insert at end.
  AddTestResultAtIndex(kDefaultResultsCount, "Four", std::string());
  EXPECT_EQ(kDefaultResultsCount + 1, [table_view numberOfRows]);
  ExpectConsistent();

  // Delete from start.
  [delegate_ appListModel]->results()->DeleteAt(0);
  EXPECT_EQ(kDefaultResultsCount, [table_view numberOfRows]);
  ExpectConsistent();

  // Test clearing results.
  [delegate_ appListModel]->results()->DeleteAll();
  EXPECT_EQ(0, [table_view numberOfRows]);
  ExpectConsistent();
}

TEST_F(AppsSearchResultsControllerTest, KeyboardSelectAndActivate) {
  NSTableView* table_view = [apps_search_results_controller_ tableView];
  EXPECT_EQ(-1, [table_view selectedRow]);

  // Pressing up when nothing is selected should select the last item.
  EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
  EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);
  [table_view deselectAll:nil];
  EXPECT_EQ(-1, [table_view selectedRow]);

  // Pressing down when nothing is selected should select the first item.
  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
  EXPECT_EQ(0, [table_view selectedRow]);

  // Pressing up should wrap around.
  EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
  EXPECT_EQ(kDefaultResultsCount - 1, [table_view selectedRow]);

  // Down should now also wrap, since the selection is at the end.
  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
  EXPECT_EQ(0, [table_view selectedRow]);

  // Regular down and up movement, ensuring the cells have correct backgrounds.
  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
  EXPECT_EQ(1, [table_view selectedRow]);
  EXPECT_EQ(NSBackgroundStyleDark, [ViewResultAt(1) backgroundStyle]);
  EXPECT_EQ(NSBackgroundStyleLight, [ViewResultAt(0) backgroundStyle]);

  EXPECT_TRUE(SimulateKeyAction(@selector(moveUp:)));
  EXPECT_EQ(0, [table_view selectedRow]);
  EXPECT_EQ(NSBackgroundStyleDark, [ViewResultAt(0) backgroundStyle]);
  EXPECT_EQ(NSBackgroundStyleLight, [ViewResultAt(1) backgroundStyle]);

  // Test activating items.
  EXPECT_TRUE(SimulateKeyAction(@selector(insertNewline:)));
  EXPECT_EQ(ModelResultAt(0), [delegate_ lastOpenedResult]);
  EXPECT_TRUE(SimulateKeyAction(@selector(moveDown:)));
  EXPECT_TRUE(SimulateKeyAction(@selector(insertNewline:)));
  EXPECT_EQ(ModelResultAt(1), [delegate_ lastOpenedResult]);
}

TEST_F(AppsSearchResultsControllerTest, ContextMenus) {
  NSTableView* table_view = [apps_search_results_controller_ tableView];
  NSEvent* mouse_in_row_0 = MouseEventInRow(table_view, 0);
  NSEvent* mouse_in_row_1 = MouseEventInRow(table_view, 1);

  NSMenu* menu = [table_view menuForEvent:mouse_in_row_0];
  EXPECT_EQ(1, [menu numberOfItems]);
  EXPECT_NSEQ(@"Menu For: Result 0", [[menu itemAtIndex:0] title]);

  // Test a context menu request while the item is still installing.
  SetMenuReadyAt(1, false);
  menu = [table_view menuForEvent:mouse_in_row_1];
  EXPECT_EQ(nil, menu);

  SetMenuReadyAt(1, true);
  menu = [table_view menuForEvent:mouse_in_row_1];
  EXPECT_EQ(1, [menu numberOfItems]);
  EXPECT_NSEQ(@"Menu For: Result 1", [[menu itemAtIndex:0] title]);
}

// Test that observing a search result item uninstall performs the search again.
TEST_F(AppsSearchResultsControllerTest, UninstallRedperformsSearch) {
  base::MessageLoopForUI message_loop;
  EXPECT_EQ(0, [delegate_ redoSearchCount]);
  ModelResultAt(0)->NotifyItemUninstalled();
  message_loop.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
  message_loop.Run();
  EXPECT_EQ(1, [delegate_ redoSearchCount]);
}

}  // namespace test
}  // namespace app_list