| OLD | NEW |
| (Empty) |
| 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 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 | |
| 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 base::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 = 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. | |
| 243 TEST_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(). | |
| 253 TEST_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. | |
| 286 TEST_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. | |
| 305 TEST_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. | |
| 330 TEST_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. | |
| 381 TEST_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 | |
| 402 namespace { | |
| 403 | |
| 404 class 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. | |
| 437 TEST_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 | |
| 504 namespace { | |
| 505 | |
| 506 class 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. | |
| 530 TEST_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. | |
| 565 TEST_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. | |
| 581 TEST_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. | |
| 711 TEST_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 ...). | |
| 745 TEST_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. | |
| 801 TEST_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 | |
| OLD | NEW |