1 // Copyright (c) 2012 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 #include "ui/views/controls/table/table_view.h"
6
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "testing/gtest/include/gtest/gtest.h"
10 #include "ui/views/controls/table/table_grouper.h"
11 #include "ui/views/controls/table/table_header.h"
12 #include "ui/views/controls/table/table_view_observer.h"
13
14 // Put the tests in the views namespace to make it easier to declare them as
15 // friend classes.
16 namespace views {
17
18 class TableViewTestHelper {
19 public:
TableViewTestHelper(TableView * table)20 explicit TableViewTestHelper(TableView* table) : table_(table) {}
21
GetPaintRegion(const gfx::Rect & bounds)22 std::string GetPaintRegion(const gfx::Rect& bounds) {
23 TableView::PaintRegion region(table_->GetPaintRegion(bounds));
24 return "rows=" + base::IntToString(region.min_row) + " " +
25 base::IntToString(region.max_row) + " cols=" +
26 base::IntToString(region.min_column) + " " +
27 base::IntToString(region.max_column);
28 }
29
visible_col_count()30 size_t visible_col_count() {
31 return table_->visible_columns().size();
32 }
33
header()34 TableHeader* header() { return table_->header_; }
35
36 private:
37 TableView* table_;
38
39 DISALLOW_COPY_AND_ASSIGN(TableViewTestHelper);
40 };
41
42 namespace {
43
44 // TestTableModel2 -------------------------------------------------------------
45
46 // Trivial TableModel implementation that is backed by a vector of vectors.
47 // Provides methods for adding/removing/changing the contents that notify the
48 // observer appropriately.
49 //
50 // Initial contents are:
51 // 0, 1
52 // 1, 1
53 // 2, 2
54 // 3, 0
55 class TestTableModel2 : public ui::TableModel {
56 public:
57 TestTableModel2();
58
59 // Adds a new row at index |row| with values |c1_value| and |c2_value|.
60 void AddRow(int row, int c1_value, int c2_value);
61
62 // Removes the row at index |row|.
63 void RemoveRow(int row);
64
65 // Changes the values of the row at |row|.
66 void ChangeRow(int row, int c1_value, int c2_value);
67
68 // ui::TableModel:
69 virtual int RowCount() OVERRIDE;
70 virtual string16 GetText(int row, int column_id) OVERRIDE;
71 virtual void SetObserver(ui::TableModelObserver* observer) OVERRIDE;
72 virtual int CompareValues(int row1, int row2, int column_id) OVERRIDE;
73
74 private:
75 ui::TableModelObserver* observer_;
76
77 // The data.
78 std::vector<std::vector<int> > rows_;
79
80 DISALLOW_COPY_AND_ASSIGN(TestTableModel2);
81 };
82
TestTableModel2()83 TestTableModel2::TestTableModel2() : observer_(NULL) {
84 AddRow(0, 0, 1);
85 AddRow(1, 1, 1);
86 AddRow(2, 2, 2);
87 AddRow(3, 3, 0);
88 }
89
AddRow(int row,int c1_value,int c2_value)90 void TestTableModel2::AddRow(int row, int c1_value, int c2_value) {
91 DCHECK(row >= 0 && row <= static_cast<int>(rows_.size()));
92 std::vector<int> new_row;
93 new_row.push_back(c1_value);
94 new_row.push_back(c2_value);
95 rows_.insert(rows_.begin() + row, new_row);
96 if (observer_)
97 observer_->OnItemsAdded(row, 1);
98 }
RemoveRow(int row)99 void TestTableModel2::RemoveRow(int row) {
100 DCHECK(row >= 0 && row <= static_cast<int>(rows_.size()));
101 rows_.erase(rows_.begin() + row);
102 if (observer_)
103 observer_->OnItemsRemoved(row, 1);
104 }
105
ChangeRow(int row,int c1_value,int c2_value)106 void TestTableModel2::ChangeRow(int row, int c1_value, int c2_value) {
107 DCHECK(row >= 0 && row < static_cast<int>(rows_.size()));
108 rows_[row][0] = c1_value;
109 rows_[row][1] = c2_value;
110 if (observer_)
111 observer_->OnItemsChanged(row, 1);
112 }
113
RowCount()114 int TestTableModel2::RowCount() {
115 return static_cast<int>(rows_.size());
116 }
117
GetText(int row,int column_id)118 string16 TestTableModel2::GetText(int row, int column_id) {
119 return base::IntToString16(rows_[row][column_id]);
120 }
121
SetObserver(ui::TableModelObserver * observer)122 void TestTableModel2::SetObserver(ui::TableModelObserver* observer) {
123 observer_ = observer;
124 }
125
CompareValues(int row1,int row2,int column_id)126 int TestTableModel2::CompareValues(int row1, int row2, int column_id) {
127 return rows_[row1][column_id] - rows_[row2][column_id];
128 }
129
130 // Returns the view to model mapping as a string.
GetViewToModelAsString(TableView * table)131 std::string GetViewToModelAsString(TableView* table) {
132 std::string result;
133 for (int i = 0; i < table->RowCount(); ++i) {
134 if (i != 0)
135 result += " ";
136 result += base::IntToString(table->ViewToModel(i));
137 }
138 return result;
139 }
140
141 // Returns the model to view mapping as a string.
GetModelToViewAsString(TableView * table)142 std::string GetModelToViewAsString(TableView* table) {
143 std::string result;
144 for (int i = 0; i < table->RowCount(); ++i) {
145 if (i != 0)
146 result += " ";
147 result += base::IntToString(table->ModelToView(i));
148 }
149 return result;
150 }
151
152 class TestTableView : public TableView {
153 public:
TestTableView(ui::TableModel * model,const std::vector<ui::TableColumn> & columns)154 TestTableView(ui::TableModel* model,
155 const std::vector<ui::TableColumn>& columns)
156 : TableView(model, columns, TEXT_ONLY, false) {
157 }
158
159 // View overrides:
HasFocus() const160 virtual bool HasFocus() const OVERRIDE {
161 // Overriden so key processing works.
162 return true;
163 }
164
165 private:
166 DISALLOW_COPY_AND_ASSIGN(TestTableView);
167 };
168
169 } // namespace
170
171 class TableViewTest : public testing::Test {
172 public:
TableViewTest()173 TableViewTest() : table_(NULL) {}
174
SetUp()175 virtual void SetUp() OVERRIDE {
176 model_.reset(new TestTableModel2);
177 std::vector<ui::TableColumn> columns(2);
178 columns[0].title = ASCIIToUTF16("Title Column 0");
179 columns[0].sortable = true;
180 columns[1].title = ASCIIToUTF16("Title Column 1");
181 columns[1].id = 1;
182 columns[1].sortable = true;
183 table_ = new TestTableView(model_.get(), columns);
184 parent_.reset(table_->CreateParentIfNecessary());
185 parent_->SetBounds(0, 0, 10000, 10000);
186 parent_->Layout();
187 helper_.reset(new TableViewTestHelper(table_));
188 }
189
ClickOnRow(int row,int flags)190 void ClickOnRow(int row, int flags) {
191 const int y = row * table_->row_height();
192 const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(0, y),
193 gfx::Point(0, y),
194 ui::EF_LEFT_MOUSE_BUTTON | flags);
195 table_->OnMousePressed(pressed);
196 }
197
TapOnRow(int row)198 void TapOnRow(int row) {
199 const int y = row * table_->row_height();
200 const ui::GestureEventDetails event_details(ui::ET_GESTURE_TAP,
201 .0f, .0f);
202 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, y, 0, base::TimeDelta(),
203 event_details, 1);
204 table_->OnGestureEvent(&tap);
205 }
206
207 // Returns the state of the selection model as a string. The format is:
208 // 'active=X anchor=X selection=X X X...'.
SelectionStateAsString() const209 std::string SelectionStateAsString() const {
210 const ui::ListSelectionModel& model(table_->selection_model());
211 std::string result = "active=" + base::IntToString(model.active()) +
212 " anchor=" + base::IntToString(model.anchor()) +
213 " selection=";
214 const ui::ListSelectionModel::SelectedIndices& selection(
215 model.selected_indices());
216 for (size_t i = 0; i < selection.size(); ++i) {
217 if (i != 0)
218 result += " ";
219 result += base::IntToString(selection[i]);
220 }
221 return result;
222 }
223
PressKey(ui::KeyboardCode code)224 void PressKey(ui::KeyboardCode code) {
225 ui::KeyEvent event(ui::ET_KEY_PRESSED, code, 0, false);
226 table_->OnKeyPressed(event);
227 }
228
229 protected:
230 scoped_ptr<TestTableModel2> model_;
231
232 // Owned by |parent_|.
233 TableView* table_;
234
235 scoped_ptr<TableViewTestHelper> helper_;
236
237 private:
238 scoped_ptr<View> parent_;
239
240 DISALLOW_COPY_AND_ASSIGN(TableViewTest);
241 };
242
243 // Verifies GetPaintRegion.
TEST_F(TableViewTest,GetPaintRegion)244 TEST_F(TableViewTest, GetPaintRegion) {
245 // Two columns should be visible.
246 EXPECT_EQ(2u, helper_->visible_col_count());
247
248 EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds()));
249 EXPECT_EQ("rows=0 4 cols=0 1",
250 helper_->GetPaintRegion(gfx::Rect(0, 0, 1, table_->height())));
251 }
252
253 // Verifies SetColumnVisibility().
TEST_F(TableViewTest,ColumnVisibility)254 TEST_F(TableViewTest, ColumnVisibility) {
255 // Two columns should be visible.
256 EXPECT_EQ(2u, helper_->visible_col_count());
257
258 // Should do nothing (column already visible).
259 table_->SetColumnVisibility(0, true);
260 EXPECT_EQ(2u, helper_->visible_col_count());
261
262 // Hide the first column.
263 table_->SetColumnVisibility(0, false);
264 ASSERT_EQ(1u, helper_->visible_col_count());
265 EXPECT_EQ(1, table_->visible_columns()[0].column.id);
266 EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds()));
267
268 // Hide the second column.
269 table_->SetColumnVisibility(1, false);
270 EXPECT_EQ(0u, helper_->visible_col_count());
271
272 // Show the second column.
273 table_->SetColumnVisibility(1, true);
274 ASSERT_EQ(1u, helper_->visible_col_count());
275 EXPECT_EQ(1, table_->visible_columns()[0].column.id);
276 EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds()));
277
278 // Show the first column.
279 table_->SetColumnVisibility(0, true);
280 ASSERT_EQ(2u, helper_->visible_col_count());
281 EXPECT_EQ(1, table_->visible_columns()[0].column.id);
282 EXPECT_EQ(0, table_->visible_columns()[1].column.id);
283 EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds()));
284 }
285
286 // Verifies resizing a column works.
TEST_F(TableViewTest,Resize)287 TEST_F(TableViewTest, Resize) {
288 const int x = table_->visible_columns()[0].width;
289 EXPECT_NE(0, x);
290 // Drag the mouse 1 pixel to the left.
291 const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(x, 0),
292 gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON);
293 helper_->header()->OnMousePressed(pressed);
294 const ui::MouseEvent dragged(ui::ET_MOUSE_DRAGGED, gfx::Point(x - 1, 0),
295 gfx::Point(x - 1, 0), ui::EF_LEFT_MOUSE_BUTTON);
296 helper_->header()->OnMouseDragged(dragged);
297
298 // This should shrink the first column and pull the second column in.
299 EXPECT_EQ(x - 1, table_->visible_columns()[0].width);
300 EXPECT_EQ(x - 1, table_->visible_columns()[1].x);
301 }
302
303 // Verifies resizing a column works with a gesture.
TEST_F(TableViewTest,ResizeViaGesture)304 TEST_F(TableViewTest, ResizeViaGesture) {
305 const int x = table_->visible_columns()[0].width;
306 EXPECT_NE(0, x);
307 // Drag the mouse 1 pixel to the left.
308 const ui::GestureEventDetails event_details(ui::ET_GESTURE_SCROLL_BEGIN,
309 .0f, .0f);
310 ui::GestureEvent scroll_begin(ui::ET_GESTURE_SCROLL_BEGIN, x, 0, 0,
311 base::TimeDelta(), event_details, 1);
312 helper_->header()->OnGestureEvent(&scroll_begin);
313 ui::GestureEvent scroll_update(ui::ET_GESTURE_SCROLL_UPDATE, x - 1, 0,
314 0, base::TimeDelta(), event_details, 1);
315 helper_->header()->OnGestureEvent(&scroll_update);
316
317 // This should shrink the first column and pull the second column in.
318 EXPECT_EQ(x - 1, table_->visible_columns()[0].width);
319 EXPECT_EQ(x - 1, table_->visible_columns()[1].x);
320 }
321
322 // Assertions for table sorting.
TEST_F(TableViewTest,Sort)323 TEST_F(TableViewTest, Sort) {
324 // Toggle the sort order of the first column, shouldn't change anything.
325 table_->ToggleSortOrder(0);
326 ASSERT_EQ(1u, table_->sort_descriptors().size());
327 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
328 EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
329 EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
330 EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
331
332 // Invert the sort (first column descending).
333 table_->ToggleSortOrder(0);
334 ASSERT_EQ(1u, table_->sort_descriptors().size());
335 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
336 EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
337 EXPECT_EQ("3 2 1 0", GetViewToModelAsString(table_));
338 EXPECT_EQ("3 2 1 0", GetModelToViewAsString(table_));
339
340 // Change cell 0x3 to -1, meaning we have 0, 1, 2, -1 (in the first column).
341 model_->ChangeRow(3, -1, 0);
342 ASSERT_EQ(1u, table_->sort_descriptors().size());
343 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
344 EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
345 EXPECT_EQ("2 1 0 3", GetViewToModelAsString(table_));
346 EXPECT_EQ("2 1 0 3", GetModelToViewAsString(table_));
347
348 // Invert sort again (first column ascending).
349 table_->ToggleSortOrder(0);
350 ASSERT_EQ(1u, table_->sort_descriptors().size());
351 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
352 EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
353 EXPECT_EQ("3 0 1 2", GetViewToModelAsString(table_));
354 EXPECT_EQ("1 2 3 0", GetModelToViewAsString(table_));
355
356 // Add a row so that model has 0, 3, 1, 2, -1.
357 model_->AddRow(1, 3, 4);
358 ASSERT_EQ(1u, table_->sort_descriptors().size());
359 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
360 EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
361 EXPECT_EQ("4 0 2 3 1", GetViewToModelAsString(table_));
362 EXPECT_EQ("1 4 2 3 0", GetModelToViewAsString(table_));
363
364 // Delete the first row, ending up with 3, 1, 2, -1.
365 model_->RemoveRow(0);
366 ASSERT_EQ(1u, table_->sort_descriptors().size());
367 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
368 EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
369 EXPECT_EQ("3 1 2 0", GetViewToModelAsString(table_));
370 EXPECT_EQ("3 1 2 0", GetModelToViewAsString(table_));
371 }
372
373 // Verfies clicking on the header sorts.
TEST_F(TableViewTest,SortOnMouse)374 TEST_F(TableViewTest, SortOnMouse) {
375 EXPECT_TRUE(table_->sort_descriptors().empty());
376
377 const int x = table_->visible_columns()[0].width / 2;
378 EXPECT_NE(0, x);
379 // Press and release the mouse.
380 const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(x, 0),
381 gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON);
382 // The header must return true, else it won't normally get the release.
383 EXPECT_TRUE(helper_->header()->OnMousePressed(pressed));
384 const ui::MouseEvent release(ui::ET_MOUSE_RELEASED, gfx::Point(x, 0),
385 gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON);
386 helper_->header()->OnMouseReleased(release);
387
388 ASSERT_EQ(1u, table_->sort_descriptors().size());
389 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
390 EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
391 }
392
393 namespace {
394
395 class TableGrouperImpl : public TableGrouper {
396 public:
TableGrouperImpl()397 TableGrouperImpl() {}
398
SetRanges(const std::vector<int> & ranges)399 void SetRanges(const std::vector<int>& ranges) {
400 ranges_ = ranges;
401 }
402
403 // TableGrouper overrides:
GetGroupRange(int model_index,GroupRange * range)404 virtual void GetGroupRange(int model_index, GroupRange* range) OVERRIDE {
405 int offset = 0;
406 size_t range_index = 0;
407 for (; range_index < ranges_.size() && offset < model_index; ++range_index)
408 offset += ranges_[range_index];
409
410 if (offset == model_index) {
411 range->start = model_index;
412 range->length = ranges_[range_index];
413 } else {
414 range->start = offset - ranges_[range_index - 1];
415 range->length = ranges_[range_index - 1];
416 }
417 }
418
419 private:
420 std::vector<int> ranges_;
421
422 DISALLOW_COPY_AND_ASSIGN(TableGrouperImpl);
423 };
424
425 } // namespace
426
427 // Assertions around grouping.
TEST_F(TableViewTest,Grouping)428 TEST_F(TableViewTest, Grouping) {
429 // Configure the grouper so that there are two groups:
430 // A 0
431 // 1
432 // B 2
433 // 3
434 TableGrouperImpl grouper;
435 std::vector<int> ranges;
436 ranges.push_back(2);
437 ranges.push_back(2);
438 grouper.SetRanges(ranges);
439 table_->SetGrouper(&grouper);
440
441 // Toggle the sort order of the first column, shouldn't change anything.
442 table_->ToggleSortOrder(0);
443 ASSERT_EQ(1u, table_->sort_descriptors().size());
444 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
445 EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
446 EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
447 EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
448
449 // Sort descending, resulting:
450 // B 2
451 // 3
452 // A 0
453 // 1
454 table_->ToggleSortOrder(0);
455 ASSERT_EQ(1u, table_->sort_descriptors().size());
456 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
457 EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
458 EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_));
459 EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_));
460
461 // Change the entry in the 4th row to -1. The model now becomes:
462 // A 0
463 // 1
464 // B 2
465 // -1
466 // Since the first entry in the range didn't change the sort isn't impacted.
467 model_->ChangeRow(3, -1, 0);
468 ASSERT_EQ(1u, table_->sort_descriptors().size());
469 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
470 EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
471 EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_));
472 EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_));
473
474 // Change the entry in the 3rd row to -1. The model now becomes:
475 // A 0
476 // 1
477 // B -1
478 // -1
479 model_->ChangeRow(2, -1, 0);
480 ASSERT_EQ(1u, table_->sort_descriptors().size());
481 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
482 EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
483 EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
484 EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
485
486 // Toggle to ascending sort.
487 table_->ToggleSortOrder(0);
488 ASSERT_EQ(1u, table_->sort_descriptors().size());
489 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
490 EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
491 EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_));
492 EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_));
493 }
494
495 namespace {
496
497 class TableViewObserverImpl : public TableViewObserver {
498 public:
TableViewObserverImpl()499 TableViewObserverImpl() : selection_changed_count_(0) {}
500
GetChangedCountAndClear()501 int GetChangedCountAndClear() {
502 const int count = selection_changed_count_;
503 selection_changed_count_ = 0;
504 return count;
505 }
506
507 // TableViewObserver overrides:
OnSelectionChanged()508 virtual void OnSelectionChanged() OVERRIDE {
509 selection_changed_count_++;
510 }
511
512 private:
513 int selection_changed_count_;
514
515 DISALLOW_COPY_AND_ASSIGN(TableViewObserverImpl);
516 };
517
518 } // namespace
519
520 // Assertions around changing the selection.
TEST_F(TableViewTest,Selection)521 TEST_F(TableViewTest, Selection) {
522 TableViewObserverImpl observer;
523 table_->SetObserver(&observer);
524
525 // Initially no selection.
526 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
527
528 // Select the last row.
529 table_->Select(3);
530 EXPECT_EQ(1, observer.GetChangedCountAndClear());
531 EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
532
533 // Change sort, shouldn't notify of change (toggle twice so that order
534 // actually changes).
535 table_->ToggleSortOrder(0);
536 table_->ToggleSortOrder(0);
537 EXPECT_EQ(0, observer.GetChangedCountAndClear());
538 EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
539
540 // Remove the selected row, this should notify of a change and update the
541 // selection.
542 model_->RemoveRow(3);
543 EXPECT_EQ(1, observer.GetChangedCountAndClear());
544 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
545
546 // Insert a row, since the selection in terms of the original model hasn't
547 // changed the observer is not notified.
548 model_->AddRow(0, 1, 2);
549 EXPECT_EQ(0, observer.GetChangedCountAndClear());
550 EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
551
552 table_->SetObserver(NULL);
553 }
554
555 // Verifies selection works by way of a gesture.
TEST_F(TableViewTest,SelectOnTap)556 TEST_F(TableViewTest, SelectOnTap) {
557 // Initially no selection.
558 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
559
560 TableViewObserverImpl observer;
561 table_->SetObserver(&observer);
562
563 // Click on the first row, should select it.
564 TapOnRow(0);
565 EXPECT_EQ(1, observer.GetChangedCountAndClear());
566 EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());
567
568 table_->SetObserver(NULL);
569 }
570
571 // Verifies up/down correctly navigates through groups.
TEST_F(TableViewTest,KeyUpDown)572 TEST_F(TableViewTest, KeyUpDown) {
573 // Configure the grouper so that there are three groups:
574 // A 0
575 // 1
576 // B 5
577 // C 2
578 // 3
579 model_->AddRow(2, 5, 0);
580 TableGrouperImpl grouper;
581 std::vector<int> ranges;
582 ranges.push_back(2);
583 ranges.push_back(1);
584 ranges.push_back(2);
585 grouper.SetRanges(ranges);
586 table_->SetGrouper(&grouper);
587
588 TableViewObserverImpl observer;
589 table_->SetObserver(&observer);
590
591 // Initially no selection.
592 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
593
594 PressKey(ui::VKEY_DOWN);
595 EXPECT_EQ(1, observer.GetChangedCountAndClear());
596 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
597
598 PressKey(ui::VKEY_DOWN);
599 EXPECT_EQ(1, observer.GetChangedCountAndClear());
600 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
601
602 PressKey(ui::VKEY_DOWN);
603 EXPECT_EQ(1, observer.GetChangedCountAndClear());
604 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
605
606 PressKey(ui::VKEY_DOWN);
607 EXPECT_EQ(1, observer.GetChangedCountAndClear());
608 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
609
610 PressKey(ui::VKEY_DOWN);
611 EXPECT_EQ(1, observer.GetChangedCountAndClear());
612 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
613
614 PressKey(ui::VKEY_DOWN);
615 EXPECT_EQ(0, observer.GetChangedCountAndClear());
616 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
617
618 PressKey(ui::VKEY_UP);
619 EXPECT_EQ(1, observer.GetChangedCountAndClear());
620 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
621
622 PressKey(ui::VKEY_UP);
623 EXPECT_EQ(1, observer.GetChangedCountAndClear());
624 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
625
626 PressKey(ui::VKEY_UP);
627 EXPECT_EQ(1, observer.GetChangedCountAndClear());
628 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
629
630 PressKey(ui::VKEY_UP);
631 EXPECT_EQ(1, observer.GetChangedCountAndClear());
632 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
633
634 PressKey(ui::VKEY_UP);
635 EXPECT_EQ(0, observer.GetChangedCountAndClear());
636 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
637
638 // Sort the table descending by column 1, view now looks like:
639 // B 5 model: 2
640 // C 2 3
641 // 3 4
642 // A 0 0
643 // 1 1
644 table_->ToggleSortOrder(0);
645 table_->ToggleSortOrder(0);
646
647 EXPECT_EQ("2 3 4 0 1", GetViewToModelAsString(table_));
648
649 table_->Select(-1);
650 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
651
652 observer.GetChangedCountAndClear();
653 // Up with nothing selected selects the first row.
654 PressKey(ui::VKEY_UP);
655 EXPECT_EQ(1, observer.GetChangedCountAndClear());
656 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
657
658 PressKey(ui::VKEY_DOWN);
659 EXPECT_EQ(1, observer.GetChangedCountAndClear());
660 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
661
662 PressKey(ui::VKEY_DOWN);
663 EXPECT_EQ(1, observer.GetChangedCountAndClear());
664 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
665
666 PressKey(ui::VKEY_DOWN);
667 EXPECT_EQ(1, observer.GetChangedCountAndClear());
668 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
669
670 PressKey(ui::VKEY_DOWN);
671 EXPECT_EQ(1, observer.GetChangedCountAndClear());
672 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
673
674 PressKey(ui::VKEY_DOWN);
675 EXPECT_EQ(0, observer.GetChangedCountAndClear());
676 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
677
678 PressKey(ui::VKEY_UP);
679 EXPECT_EQ(1, observer.GetChangedCountAndClear());
680 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
681
682 PressKey(ui::VKEY_UP);
683 EXPECT_EQ(1, observer.GetChangedCountAndClear());
684 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
685
686 PressKey(ui::VKEY_UP);
687 EXPECT_EQ(1, observer.GetChangedCountAndClear());
688 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
689
690 PressKey(ui::VKEY_UP);
691 EXPECT_EQ(1, observer.GetChangedCountAndClear());
692 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
693
694 PressKey(ui::VKEY_UP);
695 EXPECT_EQ(0, observer.GetChangedCountAndClear());
696 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
697
698 table_->SetObserver(NULL);
699 }
700
701 // Verifies home/end do the right thing.
TEST_F(TableViewTest,HomeEnd)702 TEST_F(TableViewTest, HomeEnd) {
703 // Configure the grouper so that there are three groups:
704 // A 0
705 // 1
706 // B 5
707 // C 2
708 // 3
709 model_->AddRow(2, 5, 0);
710 TableGrouperImpl grouper;
711 std::vector<int> ranges;
712 ranges.push_back(2);
713 ranges.push_back(1);
714 ranges.push_back(2);
715 grouper.SetRanges(ranges);
716 table_->SetGrouper(&grouper);
717
718 TableViewObserverImpl observer;
719 table_->SetObserver(&observer);
720
721 // Initially no selection.
722 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
723
724 PressKey(ui::VKEY_HOME);
725 EXPECT_EQ(1, observer.GetChangedCountAndClear());
726 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
727
728 PressKey(ui::VKEY_END);
729 EXPECT_EQ(1, observer.GetChangedCountAndClear());
730 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
731
732 table_->SetObserver(NULL);
733 }
734
735 // Verifies multiple selection gestures work (control-click, shift-click ...).
TEST_F(TableViewTest,Multiselection)736 TEST_F(TableViewTest, Multiselection) {
737 // Configure the grouper so that there are three groups:
738 // A 0
739 // 1
740 // B 5
741 // C 2
742 // 3
743 model_->AddRow(2, 5, 0);
744 TableGrouperImpl grouper;
745 std::vector<int> ranges;
746 ranges.push_back(2);
747 ranges.push_back(1);
748 ranges.push_back(2);
749 grouper.SetRanges(ranges);
750 table_->SetGrouper(&grouper);
751
752 // Initially no selection.
753 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
754
755 TableViewObserverImpl observer;
756 table_->SetObserver(&observer);
757
758 // Click on the first row, should select it and the second row.
759 ClickOnRow(0, 0);
760 EXPECT_EQ(1, observer.GetChangedCountAndClear());
761 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
762
763 // Click on the last row, should select it and the row before it.
764 ClickOnRow(4, 0);
765 EXPECT_EQ(1, observer.GetChangedCountAndClear());
766 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
767
768 // Shift click on the third row, should extend selection to it.
769 ClickOnRow(2, ui::EF_SHIFT_DOWN);
770 EXPECT_EQ(1, observer.GetChangedCountAndClear());
771 EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString());
772
773 // Control click on third row, should toggle it.
774 ClickOnRow(2, ui::EF_CONTROL_DOWN);
775 EXPECT_EQ(1, observer.GetChangedCountAndClear());
776 EXPECT_EQ("active=2 anchor=2 selection=3 4", SelectionStateAsString());
777
778 // Control-shift click on second row, should extend selection to it.
779 ClickOnRow(1, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
780 EXPECT_EQ(1, observer.GetChangedCountAndClear());
781 EXPECT_EQ("active=1 anchor=2 selection=0 1 2 3 4", SelectionStateAsString());
782
783 // Click on last row again.
784 ClickOnRow(4, 0);
785 EXPECT_EQ(1, observer.GetChangedCountAndClear());
786 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
787
788 table_->SetObserver(NULL);
789 }
790
791 // Verifies multiple selection gestures work when sorted.
TEST_F(TableViewTest,MultiselectionWithSort)792 TEST_F(TableViewTest, MultiselectionWithSort) {
793 // Configure the grouper so that there are three groups:
794 // A 0
795 // 1
796 // B 5
797 // C 2
798 // 3
799 model_->AddRow(2, 5, 0);
800 TableGrouperImpl grouper;
801 std::vector<int> ranges;
802 ranges.push_back(2);
803 ranges.push_back(1);
804 ranges.push_back(2);
805 grouper.SetRanges(ranges);
806 table_->SetGrouper(&grouper);
807
808 // Sort the table descending by column 1, view now looks like:
809 // B 5 model: 2
810 // C 2 3
811 // 3 4
812 // A 0 0
813 // 1 1
814 table_->ToggleSortOrder(0);
815 table_->ToggleSortOrder(0);
816
817 // Initially no selection.
818 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
819
820 TableViewObserverImpl observer;
821 table_->SetObserver(&observer);
822
823 // Click on the third row, should select it and the second row.
824 ClickOnRow(2, 0);
825 EXPECT_EQ(1, observer.GetChangedCountAndClear());
826 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
827
828 // Extend selection to first row.
829 ClickOnRow(0, ui::EF_SHIFT_DOWN);
830 EXPECT_EQ(1, observer.GetChangedCountAndClear());
831 EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString());
832
833 table_->SetObserver(NULL);
834 }
835
836 } // namespace views
837