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. 16namespace views { 17 18class 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 42namespace { 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 55class 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 base::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 83TestTableModel2::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 90void 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} 99void 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 106void 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 114int TestTableModel2::RowCount() { 115 return static_cast<int>(rows_.size()); 116} 117 118base::string16 TestTableModel2::GetText(int row, int column_id) { 119 return base::IntToString16(rows_[row][column_id]); 120} 121 122void TestTableModel2::SetObserver(ui::TableModelObserver* observer) { 123 observer_ = observer; 124} 125 126int 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. 131std::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. 142std::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 152class 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 171class 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 = base::ASCIIToUTF16("Title Column 0"); 179 columns[0].sortable = true; 180 columns[1].title = base::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 ui::EF_LEFT_MOUSE_BUTTON); 196 table_->OnMousePressed(pressed); 197 } 198 199 void TapOnRow(int row) { 200 const int y = row * table_->row_height(); 201 const ui::GestureEventDetails event_details(ui::ET_GESTURE_TAP); 202 ui::GestureEvent tap(0, y, 0, base::TimeDelta(), event_details); 203 table_->OnGestureEvent(&tap); 204 } 205 206 // Returns the state of the selection model as a string. The format is: 207 // 'active=X anchor=X selection=X X X...'. 208 std::string SelectionStateAsString() const { 209 const ui::ListSelectionModel& model(table_->selection_model()); 210 std::string result = "active=" + base::IntToString(model.active()) + 211 " anchor=" + base::IntToString(model.anchor()) + 212 " selection="; 213 const ui::ListSelectionModel::SelectedIndices& selection( 214 model.selected_indices()); 215 for (size_t i = 0; i < selection.size(); ++i) { 216 if (i != 0) 217 result += " "; 218 result += base::IntToString(selection[i]); 219 } 220 return result; 221 } 222 223 void PressKey(ui::KeyboardCode code) { 224 ui::KeyEvent event(ui::ET_KEY_PRESSED, code, ui::EF_NONE); 225 table_->OnKeyPressed(event); 226 } 227 228 protected: 229 scoped_ptr<TestTableModel2> model_; 230 231 // Owned by |parent_|. 232 TableView* table_; 233 234 scoped_ptr<TableViewTestHelper> helper_; 235 236 private: 237 scoped_ptr<View> parent_; 238 239 DISALLOW_COPY_AND_ASSIGN(TableViewTest); 240}; 241 242// Verifies GetPaintRegion. 243TEST_F(TableViewTest, GetPaintRegion) { 244 // Two columns should be visible. 245 EXPECT_EQ(2u, helper_->visible_col_count()); 246 247 EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds())); 248 EXPECT_EQ("rows=0 4 cols=0 1", 249 helper_->GetPaintRegion(gfx::Rect(0, 0, 1, table_->height()))); 250} 251 252// Verifies SetColumnVisibility(). 253TEST_F(TableViewTest, ColumnVisibility) { 254 // Two columns should be visible. 255 EXPECT_EQ(2u, helper_->visible_col_count()); 256 257 // Should do nothing (column already visible). 258 table_->SetColumnVisibility(0, true); 259 EXPECT_EQ(2u, helper_->visible_col_count()); 260 261 // Hide the first column. 262 table_->SetColumnVisibility(0, false); 263 ASSERT_EQ(1u, helper_->visible_col_count()); 264 EXPECT_EQ(1, table_->visible_columns()[0].column.id); 265 EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds())); 266 267 // Hide the second column. 268 table_->SetColumnVisibility(1, false); 269 EXPECT_EQ(0u, helper_->visible_col_count()); 270 271 // Show the second column. 272 table_->SetColumnVisibility(1, true); 273 ASSERT_EQ(1u, helper_->visible_col_count()); 274 EXPECT_EQ(1, table_->visible_columns()[0].column.id); 275 EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds())); 276 277 // Show the first column. 278 table_->SetColumnVisibility(0, true); 279 ASSERT_EQ(2u, helper_->visible_col_count()); 280 EXPECT_EQ(1, table_->visible_columns()[0].column.id); 281 EXPECT_EQ(0, table_->visible_columns()[1].column.id); 282 EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds())); 283} 284 285// Verifies resizing a column works. 286TEST_F(TableViewTest, Resize) { 287 const int x = table_->visible_columns()[0].width; 288 EXPECT_NE(0, x); 289 // Drag the mouse 1 pixel to the left. 290 const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(x, 0), 291 gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON, 292 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 0); 297 helper_->header()->OnMouseDragged(dragged); 298 299 // This should shrink the first column and pull the second column in. 300 EXPECT_EQ(x - 1, table_->visible_columns()[0].width); 301 EXPECT_EQ(x - 1, table_->visible_columns()[1].x); 302} 303 304// Verifies resizing a column works with a gesture. 305TEST_F(TableViewTest, ResizeViaGesture) { 306 const int x = table_->visible_columns()[0].width; 307 EXPECT_NE(0, x); 308 // Drag the mouse 1 pixel to the left. 309 ui::GestureEvent scroll_begin( 310 x, 311 0, 312 0, 313 base::TimeDelta(), 314 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN)); 315 helper_->header()->OnGestureEvent(&scroll_begin); 316 ui::GestureEvent scroll_update( 317 x - 1, 318 0, 319 0, 320 base::TimeDelta(), 321 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE)); 322 helper_->header()->OnGestureEvent(&scroll_update); 323 324 // This should shrink the first column and pull the second column in. 325 EXPECT_EQ(x - 1, table_->visible_columns()[0].width); 326 EXPECT_EQ(x - 1, table_->visible_columns()[1].x); 327} 328 329// Assertions for table sorting. 330TEST_F(TableViewTest, Sort) { 331 // Toggle the sort order of the first column, shouldn't change anything. 332 table_->ToggleSortOrder(0); 333 ASSERT_EQ(1u, table_->sort_descriptors().size()); 334 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 335 EXPECT_TRUE(table_->sort_descriptors()[0].ascending); 336 EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_)); 337 EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_)); 338 339 // Invert the sort (first column descending). 340 table_->ToggleSortOrder(0); 341 ASSERT_EQ(1u, table_->sort_descriptors().size()); 342 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 343 EXPECT_FALSE(table_->sort_descriptors()[0].ascending); 344 EXPECT_EQ("3 2 1 0", GetViewToModelAsString(table_)); 345 EXPECT_EQ("3 2 1 0", GetModelToViewAsString(table_)); 346 347 // Change cell 0x3 to -1, meaning we have 0, 1, 2, -1 (in the first column). 348 model_->ChangeRow(3, -1, 0); 349 ASSERT_EQ(1u, table_->sort_descriptors().size()); 350 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 351 EXPECT_FALSE(table_->sort_descriptors()[0].ascending); 352 EXPECT_EQ("2 1 0 3", GetViewToModelAsString(table_)); 353 EXPECT_EQ("2 1 0 3", GetModelToViewAsString(table_)); 354 355 // Invert sort again (first column ascending). 356 table_->ToggleSortOrder(0); 357 ASSERT_EQ(1u, table_->sort_descriptors().size()); 358 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 359 EXPECT_TRUE(table_->sort_descriptors()[0].ascending); 360 EXPECT_EQ("3 0 1 2", GetViewToModelAsString(table_)); 361 EXPECT_EQ("1 2 3 0", GetModelToViewAsString(table_)); 362 363 // Add a row so that model has 0, 3, 1, 2, -1. 364 model_->AddRow(1, 3, 4); 365 ASSERT_EQ(1u, table_->sort_descriptors().size()); 366 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 367 EXPECT_TRUE(table_->sort_descriptors()[0].ascending); 368 EXPECT_EQ("4 0 2 3 1", GetViewToModelAsString(table_)); 369 EXPECT_EQ("1 4 2 3 0", GetModelToViewAsString(table_)); 370 371 // Delete the first row, ending up with 3, 1, 2, -1. 372 model_->RemoveRow(0); 373 ASSERT_EQ(1u, table_->sort_descriptors().size()); 374 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 375 EXPECT_TRUE(table_->sort_descriptors()[0].ascending); 376 EXPECT_EQ("3 1 2 0", GetViewToModelAsString(table_)); 377 EXPECT_EQ("3 1 2 0", GetModelToViewAsString(table_)); 378} 379 380// Verfies clicking on the header sorts. 381TEST_F(TableViewTest, SortOnMouse) { 382 EXPECT_TRUE(table_->sort_descriptors().empty()); 383 384 const int x = table_->visible_columns()[0].width / 2; 385 EXPECT_NE(0, x); 386 // Press and release the mouse. 387 const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(x, 0), 388 gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON, 389 ui::EF_LEFT_MOUSE_BUTTON); 390 // The header must return true, else it won't normally get the release. 391 EXPECT_TRUE(helper_->header()->OnMousePressed(pressed)); 392 const ui::MouseEvent release(ui::ET_MOUSE_RELEASED, gfx::Point(x, 0), 393 gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON, 394 ui::EF_LEFT_MOUSE_BUTTON); 395 helper_->header()->OnMouseReleased(release); 396 397 ASSERT_EQ(1u, table_->sort_descriptors().size()); 398 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 399 EXPECT_TRUE(table_->sort_descriptors()[0].ascending); 400} 401 402namespace { 403 404class TableGrouperImpl : public TableGrouper { 405 public: 406 TableGrouperImpl() {} 407 408 void SetRanges(const std::vector<int>& ranges) { 409 ranges_ = ranges; 410 } 411 412 // TableGrouper overrides: 413 virtual void GetGroupRange(int model_index, GroupRange* range) OVERRIDE { 414 int offset = 0; 415 size_t range_index = 0; 416 for (; range_index < ranges_.size() && offset < model_index; ++range_index) 417 offset += ranges_[range_index]; 418 419 if (offset == model_index) { 420 range->start = model_index; 421 range->length = ranges_[range_index]; 422 } else { 423 range->start = offset - ranges_[range_index - 1]; 424 range->length = ranges_[range_index - 1]; 425 } 426 } 427 428 private: 429 std::vector<int> ranges_; 430 431 DISALLOW_COPY_AND_ASSIGN(TableGrouperImpl); 432}; 433 434} // namespace 435 436// Assertions around grouping. 437TEST_F(TableViewTest, Grouping) { 438 // Configure the grouper so that there are two groups: 439 // A 0 440 // 1 441 // B 2 442 // 3 443 TableGrouperImpl grouper; 444 std::vector<int> ranges; 445 ranges.push_back(2); 446 ranges.push_back(2); 447 grouper.SetRanges(ranges); 448 table_->SetGrouper(&grouper); 449 450 // Toggle the sort order of the first column, shouldn't change anything. 451 table_->ToggleSortOrder(0); 452 ASSERT_EQ(1u, table_->sort_descriptors().size()); 453 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 454 EXPECT_TRUE(table_->sort_descriptors()[0].ascending); 455 EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_)); 456 EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_)); 457 458 // Sort descending, resulting: 459 // B 2 460 // 3 461 // A 0 462 // 1 463 table_->ToggleSortOrder(0); 464 ASSERT_EQ(1u, table_->sort_descriptors().size()); 465 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 466 EXPECT_FALSE(table_->sort_descriptors()[0].ascending); 467 EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_)); 468 EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_)); 469 470 // Change the entry in the 4th row to -1. The model now becomes: 471 // A 0 472 // 1 473 // B 2 474 // -1 475 // Since the first entry in the range didn't change the sort isn't impacted. 476 model_->ChangeRow(3, -1, 0); 477 ASSERT_EQ(1u, table_->sort_descriptors().size()); 478 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 479 EXPECT_FALSE(table_->sort_descriptors()[0].ascending); 480 EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_)); 481 EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_)); 482 483 // Change the entry in the 3rd row to -1. The model now becomes: 484 // A 0 485 // 1 486 // B -1 487 // -1 488 model_->ChangeRow(2, -1, 0); 489 ASSERT_EQ(1u, table_->sort_descriptors().size()); 490 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 491 EXPECT_FALSE(table_->sort_descriptors()[0].ascending); 492 EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_)); 493 EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_)); 494 495 // Toggle to ascending sort. 496 table_->ToggleSortOrder(0); 497 ASSERT_EQ(1u, table_->sort_descriptors().size()); 498 EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); 499 EXPECT_TRUE(table_->sort_descriptors()[0].ascending); 500 EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_)); 501 EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_)); 502} 503 504namespace { 505 506class TableViewObserverImpl : public TableViewObserver { 507 public: 508 TableViewObserverImpl() : selection_changed_count_(0) {} 509 510 int GetChangedCountAndClear() { 511 const int count = selection_changed_count_; 512 selection_changed_count_ = 0; 513 return count; 514 } 515 516 // TableViewObserver overrides: 517 virtual void OnSelectionChanged() OVERRIDE { 518 selection_changed_count_++; 519 } 520 521 private: 522 int selection_changed_count_; 523 524 DISALLOW_COPY_AND_ASSIGN(TableViewObserverImpl); 525}; 526 527} // namespace 528 529// Assertions around changing the selection. 530TEST_F(TableViewTest, Selection) { 531 TableViewObserverImpl observer; 532 table_->SetObserver(&observer); 533 534 // Initially no selection. 535 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); 536 537 // Select the last row. 538 table_->Select(3); 539 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 540 EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString()); 541 542 // Change sort, shouldn't notify of change (toggle twice so that order 543 // actually changes). 544 table_->ToggleSortOrder(0); 545 table_->ToggleSortOrder(0); 546 EXPECT_EQ(0, observer.GetChangedCountAndClear()); 547 EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString()); 548 549 // Remove the selected row, this should notify of a change and update the 550 // selection. 551 model_->RemoveRow(3); 552 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 553 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); 554 555 // Insert a row, since the selection in terms of the original model hasn't 556 // changed the observer is not notified. 557 model_->AddRow(0, 1, 2); 558 EXPECT_EQ(0, observer.GetChangedCountAndClear()); 559 EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString()); 560 561 table_->SetObserver(NULL); 562} 563 564// Verifies selection works by way of a gesture. 565TEST_F(TableViewTest, SelectOnTap) { 566 // Initially no selection. 567 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); 568 569 TableViewObserverImpl observer; 570 table_->SetObserver(&observer); 571 572 // Click on the first row, should select it. 573 TapOnRow(0); 574 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 575 EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString()); 576 577 table_->SetObserver(NULL); 578} 579 580// Verifies up/down correctly navigates through groups. 581TEST_F(TableViewTest, KeyUpDown) { 582 // Configure the grouper so that there are three groups: 583 // A 0 584 // 1 585 // B 5 586 // C 2 587 // 3 588 model_->AddRow(2, 5, 0); 589 TableGrouperImpl grouper; 590 std::vector<int> ranges; 591 ranges.push_back(2); 592 ranges.push_back(1); 593 ranges.push_back(2); 594 grouper.SetRanges(ranges); 595 table_->SetGrouper(&grouper); 596 597 TableViewObserverImpl observer; 598 table_->SetObserver(&observer); 599 600 // Initially no selection. 601 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); 602 603 PressKey(ui::VKEY_DOWN); 604 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 605 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); 606 607 PressKey(ui::VKEY_DOWN); 608 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 609 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); 610 611 PressKey(ui::VKEY_DOWN); 612 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 613 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); 614 615 PressKey(ui::VKEY_DOWN); 616 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 617 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); 618 619 PressKey(ui::VKEY_DOWN); 620 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 621 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 622 623 PressKey(ui::VKEY_DOWN); 624 EXPECT_EQ(0, observer.GetChangedCountAndClear()); 625 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 626 627 PressKey(ui::VKEY_UP); 628 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 629 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); 630 631 PressKey(ui::VKEY_UP); 632 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 633 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); 634 635 PressKey(ui::VKEY_UP); 636 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 637 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); 638 639 PressKey(ui::VKEY_UP); 640 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 641 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); 642 643 PressKey(ui::VKEY_UP); 644 EXPECT_EQ(0, observer.GetChangedCountAndClear()); 645 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); 646 647 // Sort the table descending by column 1, view now looks like: 648 // B 5 model: 2 649 // C 2 3 650 // 3 4 651 // A 0 0 652 // 1 1 653 table_->ToggleSortOrder(0); 654 table_->ToggleSortOrder(0); 655 656 EXPECT_EQ("2 3 4 0 1", GetViewToModelAsString(table_)); 657 658 table_->Select(-1); 659 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); 660 661 observer.GetChangedCountAndClear(); 662 // Up with nothing selected selects the first row. 663 PressKey(ui::VKEY_UP); 664 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 665 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); 666 667 PressKey(ui::VKEY_DOWN); 668 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 669 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); 670 671 PressKey(ui::VKEY_DOWN); 672 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 673 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 674 675 PressKey(ui::VKEY_DOWN); 676 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 677 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); 678 679 PressKey(ui::VKEY_DOWN); 680 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 681 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); 682 683 PressKey(ui::VKEY_DOWN); 684 EXPECT_EQ(0, observer.GetChangedCountAndClear()); 685 EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); 686 687 PressKey(ui::VKEY_UP); 688 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 689 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); 690 691 PressKey(ui::VKEY_UP); 692 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 693 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 694 695 PressKey(ui::VKEY_UP); 696 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 697 EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); 698 699 PressKey(ui::VKEY_UP); 700 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 701 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); 702 703 PressKey(ui::VKEY_UP); 704 EXPECT_EQ(0, observer.GetChangedCountAndClear()); 705 EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); 706 707 table_->SetObserver(NULL); 708} 709 710// Verifies home/end do the right thing. 711TEST_F(TableViewTest, HomeEnd) { 712 // Configure the grouper so that there are three groups: 713 // A 0 714 // 1 715 // B 5 716 // C 2 717 // 3 718 model_->AddRow(2, 5, 0); 719 TableGrouperImpl grouper; 720 std::vector<int> ranges; 721 ranges.push_back(2); 722 ranges.push_back(1); 723 ranges.push_back(2); 724 grouper.SetRanges(ranges); 725 table_->SetGrouper(&grouper); 726 727 TableViewObserverImpl observer; 728 table_->SetObserver(&observer); 729 730 // Initially no selection. 731 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); 732 733 PressKey(ui::VKEY_HOME); 734 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 735 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); 736 737 PressKey(ui::VKEY_END); 738 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 739 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 740 741 table_->SetObserver(NULL); 742} 743 744// Verifies multiple selection gestures work (control-click, shift-click ...). 745TEST_F(TableViewTest, Multiselection) { 746 // Configure the grouper so that there are three groups: 747 // A 0 748 // 1 749 // B 5 750 // C 2 751 // 3 752 model_->AddRow(2, 5, 0); 753 TableGrouperImpl grouper; 754 std::vector<int> ranges; 755 ranges.push_back(2); 756 ranges.push_back(1); 757 ranges.push_back(2); 758 grouper.SetRanges(ranges); 759 table_->SetGrouper(&grouper); 760 761 // Initially no selection. 762 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); 763 764 TableViewObserverImpl observer; 765 table_->SetObserver(&observer); 766 767 // Click on the first row, should select it and the second row. 768 ClickOnRow(0, 0); 769 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 770 EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); 771 772 // Click on the last row, should select it and the row before it. 773 ClickOnRow(4, 0); 774 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 775 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 776 777 // Shift click on the third row, should extend selection to it. 778 ClickOnRow(2, ui::EF_SHIFT_DOWN); 779 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 780 EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString()); 781 782 // Control click on third row, should toggle it. 783 ClickOnRow(2, ui::EF_CONTROL_DOWN); 784 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 785 EXPECT_EQ("active=2 anchor=2 selection=3 4", SelectionStateAsString()); 786 787 // Control-shift click on second row, should extend selection to it. 788 ClickOnRow(1, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); 789 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 790 EXPECT_EQ("active=1 anchor=2 selection=0 1 2 3 4", SelectionStateAsString()); 791 792 // Click on last row again. 793 ClickOnRow(4, 0); 794 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 795 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 796 797 table_->SetObserver(NULL); 798} 799 800// Verifies multiple selection gestures work when sorted. 801TEST_F(TableViewTest, MultiselectionWithSort) { 802 // Configure the grouper so that there are three groups: 803 // A 0 804 // 1 805 // B 5 806 // C 2 807 // 3 808 model_->AddRow(2, 5, 0); 809 TableGrouperImpl grouper; 810 std::vector<int> ranges; 811 ranges.push_back(2); 812 ranges.push_back(1); 813 ranges.push_back(2); 814 grouper.SetRanges(ranges); 815 table_->SetGrouper(&grouper); 816 817 // Sort the table descending by column 1, view now looks like: 818 // B 5 model: 2 819 // C 2 3 820 // 3 4 821 // A 0 0 822 // 1 1 823 table_->ToggleSortOrder(0); 824 table_->ToggleSortOrder(0); 825 826 // Initially no selection. 827 EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); 828 829 TableViewObserverImpl observer; 830 table_->SetObserver(&observer); 831 832 // Click on the third row, should select it and the second row. 833 ClickOnRow(2, 0); 834 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 835 EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); 836 837 // Extend selection to first row. 838 ClickOnRow(0, ui::EF_SHIFT_DOWN); 839 EXPECT_EQ(1, observer.GetChangedCountAndClear()); 840 EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString()); 841 842 table_->SetObserver(NULL); 843} 844 845} // namespace views 846