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