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: 20 explicit TableViewTestHelper(TableView* table) : table_(table) {} 21 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 30 size_t visible_col_count() { 31 return table_->visible_columns().size(); 32 } 33 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 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 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 } 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 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 114 int TestTableModel2::RowCount() { 115 return static_cast<int>(rows_.size()); 116 } 117 118 string16 TestTableModel2::GetText(int row, int column_id) { 119 return base::IntToString16(rows_[row][column_id]); 120 } 121 122 void TestTableModel2::SetObserver(ui::TableModelObserver* observer) { 123 observer_ = observer; 124 } 125 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. 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. 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: 154 TestTableView(ui::TableModel* model, 155 const std::vector<ui::TableColumn>& columns) 156 : TableView(model, columns, TEXT_ONLY, false) { 157 } 158 159 // View overrides: 160 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: 173 TableViewTest() : table_(NULL) {} 174 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 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 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...'. 209 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 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. 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(). 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. 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. 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. 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. 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: 397 TableGrouperImpl() {} 398 399 void SetRanges(const std::vector<int>& ranges) { 400 ranges_ = ranges; 401 } 402 403 // TableGrouper overrides: 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. 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: 499 TableViewObserverImpl() : selection_changed_count_(0) {} 500 501 int GetChangedCountAndClear() { 502 const int count = selection_changed_count_; 503 selection_changed_count_ = 0; 504 return count; 505 } 506 507 // TableViewObserver overrides: 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. 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. 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. 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. 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 ...). 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. 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