| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/table/table_view.h" |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/views/controls/table/table_grouper.h" |
| #include "ui/views/controls/table/table_header.h" |
| #include "ui/views/controls/table/table_view_observer.h" |
| |
| // Put the tests in the views namespace to make it easier to declare them as |
| // friend classes. |
| namespace views { |
| |
| class TableViewTestHelper { |
| public: |
| explicit TableViewTestHelper(TableView* table) : table_(table) {} |
| |
| std::string GetPaintRegion(const gfx::Rect& bounds) { |
| TableView::PaintRegion region(table_->GetPaintRegion(bounds)); |
| return "rows=" + base::IntToString(region.min_row) + " " + |
| base::IntToString(region.max_row) + " cols=" + |
| base::IntToString(region.min_column) + " " + |
| base::IntToString(region.max_column); |
| } |
| |
| size_t visible_col_count() { |
| return table_->visible_columns().size(); |
| } |
| |
| TableHeader* header() { return table_->header_; } |
| |
| private: |
| TableView* table_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TableViewTestHelper); |
| }; |
| |
| namespace { |
| |
| // TestTableModel2 ------------------------------------------------------------- |
| |
| // Trivial TableModel implementation that is backed by a vector of vectors. |
| // Provides methods for adding/removing/changing the contents that notify the |
| // observer appropriately. |
| // |
| // Initial contents are: |
| // 0, 1 |
| // 1, 1 |
| // 2, 2 |
| // 3, 0 |
| class TestTableModel2 : public ui::TableModel { |
| public: |
| TestTableModel2(); |
| |
| // Adds a new row at index |row| with values |c1_value| and |c2_value|. |
| void AddRow(int row, int c1_value, int c2_value); |
| |
| // Removes the row at index |row|. |
| void RemoveRow(int row); |
| |
| // Changes the values of the row at |row|. |
| void ChangeRow(int row, int c1_value, int c2_value); |
| |
| // ui::TableModel: |
| virtual int RowCount() override; |
| virtual base::string16 GetText(int row, int column_id) override; |
| virtual void SetObserver(ui::TableModelObserver* observer) override; |
| virtual int CompareValues(int row1, int row2, int column_id) override; |
| |
| private: |
| ui::TableModelObserver* observer_; |
| |
| // The data. |
| std::vector<std::vector<int> > rows_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestTableModel2); |
| }; |
| |
| TestTableModel2::TestTableModel2() : observer_(NULL) { |
| AddRow(0, 0, 1); |
| AddRow(1, 1, 1); |
| AddRow(2, 2, 2); |
| AddRow(3, 3, 0); |
| } |
| |
| void TestTableModel2::AddRow(int row, int c1_value, int c2_value) { |
| DCHECK(row >= 0 && row <= static_cast<int>(rows_.size())); |
| std::vector<int> new_row; |
| new_row.push_back(c1_value); |
| new_row.push_back(c2_value); |
| rows_.insert(rows_.begin() + row, new_row); |
| if (observer_) |
| observer_->OnItemsAdded(row, 1); |
| } |
| void TestTableModel2::RemoveRow(int row) { |
| DCHECK(row >= 0 && row <= static_cast<int>(rows_.size())); |
| rows_.erase(rows_.begin() + row); |
| if (observer_) |
| observer_->OnItemsRemoved(row, 1); |
| } |
| |
| void TestTableModel2::ChangeRow(int row, int c1_value, int c2_value) { |
| DCHECK(row >= 0 && row < static_cast<int>(rows_.size())); |
| rows_[row][0] = c1_value; |
| rows_[row][1] = c2_value; |
| if (observer_) |
| observer_->OnItemsChanged(row, 1); |
| } |
| |
| int TestTableModel2::RowCount() { |
| return static_cast<int>(rows_.size()); |
| } |
| |
| base::string16 TestTableModel2::GetText(int row, int column_id) { |
| return base::IntToString16(rows_[row][column_id]); |
| } |
| |
| void TestTableModel2::SetObserver(ui::TableModelObserver* observer) { |
| observer_ = observer; |
| } |
| |
| int TestTableModel2::CompareValues(int row1, int row2, int column_id) { |
| return rows_[row1][column_id] - rows_[row2][column_id]; |
| } |
| |
| // Returns the view to model mapping as a string. |
| std::string GetViewToModelAsString(TableView* table) { |
| std::string result; |
| for (int i = 0; i < table->RowCount(); ++i) { |
| if (i != 0) |
| result += " "; |
| result += base::IntToString(table->ViewToModel(i)); |
| } |
| return result; |
| } |
| |
| // Returns the model to view mapping as a string. |
| std::string GetModelToViewAsString(TableView* table) { |
| std::string result; |
| for (int i = 0; i < table->RowCount(); ++i) { |
| if (i != 0) |
| result += " "; |
| result += base::IntToString(table->ModelToView(i)); |
| } |
| return result; |
| } |
| |
| class TestTableView : public TableView { |
| public: |
| TestTableView(ui::TableModel* model, |
| const std::vector<ui::TableColumn>& columns) |
| : TableView(model, columns, TEXT_ONLY, false) { |
| } |
| |
| // View overrides: |
| virtual bool HasFocus() const override { |
| // Overriden so key processing works. |
| return true; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestTableView); |
| }; |
| |
| } // namespace |
| |
| class TableViewTest : public testing::Test { |
| public: |
| TableViewTest() : table_(NULL) {} |
| |
| virtual void SetUp() override { |
| model_.reset(new TestTableModel2); |
| std::vector<ui::TableColumn> columns(2); |
| columns[0].title = base::ASCIIToUTF16("Title Column 0"); |
| columns[0].sortable = true; |
| columns[1].title = base::ASCIIToUTF16("Title Column 1"); |
| columns[1].id = 1; |
| columns[1].sortable = true; |
| table_ = new TestTableView(model_.get(), columns); |
| parent_.reset(table_->CreateParentIfNecessary()); |
| parent_->SetBounds(0, 0, 10000, 10000); |
| parent_->Layout(); |
| helper_.reset(new TableViewTestHelper(table_)); |
| } |
| |
| void ClickOnRow(int row, int flags) { |
| const int y = row * table_->row_height(); |
| const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(0, y), |
| gfx::Point(0, y), |
| ui::EF_LEFT_MOUSE_BUTTON | flags, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| table_->OnMousePressed(pressed); |
| } |
| |
| void TapOnRow(int row) { |
| const int y = row * table_->row_height(); |
| const ui::GestureEventDetails event_details(ui::ET_GESTURE_TAP); |
| ui::GestureEvent tap(0, y, 0, base::TimeDelta(), event_details); |
| table_->OnGestureEvent(&tap); |
| } |
| |
| // Returns the state of the selection model as a string. The format is: |
| // 'active=X anchor=X selection=X X X...'. |
| std::string SelectionStateAsString() const { |
| const ui::ListSelectionModel& model(table_->selection_model()); |
| std::string result = "active=" + base::IntToString(model.active()) + |
| " anchor=" + base::IntToString(model.anchor()) + |
| " selection="; |
| const ui::ListSelectionModel::SelectedIndices& selection( |
| model.selected_indices()); |
| for (size_t i = 0; i < selection.size(); ++i) { |
| if (i != 0) |
| result += " "; |
| result += base::IntToString(selection[i]); |
| } |
| return result; |
| } |
| |
| void PressKey(ui::KeyboardCode code) { |
| ui::KeyEvent event(ui::ET_KEY_PRESSED, code, ui::EF_NONE); |
| table_->OnKeyPressed(event); |
| } |
| |
| protected: |
| scoped_ptr<TestTableModel2> model_; |
| |
| // Owned by |parent_|. |
| TableView* table_; |
| |
| scoped_ptr<TableViewTestHelper> helper_; |
| |
| private: |
| scoped_ptr<View> parent_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TableViewTest); |
| }; |
| |
| // Verifies GetPaintRegion. |
| TEST_F(TableViewTest, GetPaintRegion) { |
| // Two columns should be visible. |
| EXPECT_EQ(2u, helper_->visible_col_count()); |
| |
| EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds())); |
| EXPECT_EQ("rows=0 4 cols=0 1", |
| helper_->GetPaintRegion(gfx::Rect(0, 0, 1, table_->height()))); |
| } |
| |
| // Verifies SetColumnVisibility(). |
| TEST_F(TableViewTest, ColumnVisibility) { |
| // Two columns should be visible. |
| EXPECT_EQ(2u, helper_->visible_col_count()); |
| |
| // Should do nothing (column already visible). |
| table_->SetColumnVisibility(0, true); |
| EXPECT_EQ(2u, helper_->visible_col_count()); |
| |
| // Hide the first column. |
| table_->SetColumnVisibility(0, false); |
| ASSERT_EQ(1u, helper_->visible_col_count()); |
| EXPECT_EQ(1, table_->visible_columns()[0].column.id); |
| EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds())); |
| |
| // Hide the second column. |
| table_->SetColumnVisibility(1, false); |
| EXPECT_EQ(0u, helper_->visible_col_count()); |
| |
| // Show the second column. |
| table_->SetColumnVisibility(1, true); |
| ASSERT_EQ(1u, helper_->visible_col_count()); |
| EXPECT_EQ(1, table_->visible_columns()[0].column.id); |
| EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds())); |
| |
| // Show the first column. |
| table_->SetColumnVisibility(0, true); |
| ASSERT_EQ(2u, helper_->visible_col_count()); |
| EXPECT_EQ(1, table_->visible_columns()[0].column.id); |
| EXPECT_EQ(0, table_->visible_columns()[1].column.id); |
| EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds())); |
| } |
| |
| // Verifies resizing a column works. |
| TEST_F(TableViewTest, Resize) { |
| const int x = table_->visible_columns()[0].width; |
| EXPECT_NE(0, x); |
| // Drag the mouse 1 pixel to the left. |
| const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(x, 0), |
| gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| helper_->header()->OnMousePressed(pressed); |
| const ui::MouseEvent dragged(ui::ET_MOUSE_DRAGGED, gfx::Point(x - 1, 0), |
| gfx::Point(x - 1, 0), ui::EF_LEFT_MOUSE_BUTTON, |
| 0); |
| helper_->header()->OnMouseDragged(dragged); |
| |
| // This should shrink the first column and pull the second column in. |
| EXPECT_EQ(x - 1, table_->visible_columns()[0].width); |
| EXPECT_EQ(x - 1, table_->visible_columns()[1].x); |
| } |
| |
| // Verifies resizing a column works with a gesture. |
| TEST_F(TableViewTest, ResizeViaGesture) { |
| const int x = table_->visible_columns()[0].width; |
| EXPECT_NE(0, x); |
| // Drag the mouse 1 pixel to the left. |
| ui::GestureEvent scroll_begin( |
| x, |
| 0, |
| 0, |
| base::TimeDelta(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN)); |
| helper_->header()->OnGestureEvent(&scroll_begin); |
| ui::GestureEvent scroll_update( |
| x - 1, |
| 0, |
| 0, |
| base::TimeDelta(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE)); |
| helper_->header()->OnGestureEvent(&scroll_update); |
| |
| // This should shrink the first column and pull the second column in. |
| EXPECT_EQ(x - 1, table_->visible_columns()[0].width); |
| EXPECT_EQ(x - 1, table_->visible_columns()[1].x); |
| } |
| |
| // Assertions for table sorting. |
| TEST_F(TableViewTest, Sort) { |
| // Toggle the sort order of the first column, shouldn't change anything. |
| table_->ToggleSortOrder(0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_TRUE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_)); |
| EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_)); |
| |
| // Invert the sort (first column descending). |
| table_->ToggleSortOrder(0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_FALSE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("3 2 1 0", GetViewToModelAsString(table_)); |
| EXPECT_EQ("3 2 1 0", GetModelToViewAsString(table_)); |
| |
| // Change cell 0x3 to -1, meaning we have 0, 1, 2, -1 (in the first column). |
| model_->ChangeRow(3, -1, 0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_FALSE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("2 1 0 3", GetViewToModelAsString(table_)); |
| EXPECT_EQ("2 1 0 3", GetModelToViewAsString(table_)); |
| |
| // Invert sort again (first column ascending). |
| table_->ToggleSortOrder(0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_TRUE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("3 0 1 2", GetViewToModelAsString(table_)); |
| EXPECT_EQ("1 2 3 0", GetModelToViewAsString(table_)); |
| |
| // Add a row so that model has 0, 3, 1, 2, -1. |
| model_->AddRow(1, 3, 4); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_TRUE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("4 0 2 3 1", GetViewToModelAsString(table_)); |
| EXPECT_EQ("1 4 2 3 0", GetModelToViewAsString(table_)); |
| |
| // Delete the first row, ending up with 3, 1, 2, -1. |
| model_->RemoveRow(0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_TRUE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("3 1 2 0", GetViewToModelAsString(table_)); |
| EXPECT_EQ("3 1 2 0", GetModelToViewAsString(table_)); |
| } |
| |
| // Verfies clicking on the header sorts. |
| TEST_F(TableViewTest, SortOnMouse) { |
| EXPECT_TRUE(table_->sort_descriptors().empty()); |
| |
| const int x = table_->visible_columns()[0].width / 2; |
| EXPECT_NE(0, x); |
| // Press and release the mouse. |
| const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(x, 0), |
| gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| // The header must return true, else it won't normally get the release. |
| EXPECT_TRUE(helper_->header()->OnMousePressed(pressed)); |
| const ui::MouseEvent release(ui::ET_MOUSE_RELEASED, gfx::Point(x, 0), |
| gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| helper_->header()->OnMouseReleased(release); |
| |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_TRUE(table_->sort_descriptors()[0].ascending); |
| } |
| |
| namespace { |
| |
| class TableGrouperImpl : public TableGrouper { |
| public: |
| TableGrouperImpl() {} |
| |
| void SetRanges(const std::vector<int>& ranges) { |
| ranges_ = ranges; |
| } |
| |
| // TableGrouper overrides: |
| virtual void GetGroupRange(int model_index, GroupRange* range) override { |
| int offset = 0; |
| size_t range_index = 0; |
| for (; range_index < ranges_.size() && offset < model_index; ++range_index) |
| offset += ranges_[range_index]; |
| |
| if (offset == model_index) { |
| range->start = model_index; |
| range->length = ranges_[range_index]; |
| } else { |
| range->start = offset - ranges_[range_index - 1]; |
| range->length = ranges_[range_index - 1]; |
| } |
| } |
| |
| private: |
| std::vector<int> ranges_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TableGrouperImpl); |
| }; |
| |
| } // namespace |
| |
| // Assertions around grouping. |
| TEST_F(TableViewTest, Grouping) { |
| // Configure the grouper so that there are two groups: |
| // A 0 |
| // 1 |
| // B 2 |
| // 3 |
| TableGrouperImpl grouper; |
| std::vector<int> ranges; |
| ranges.push_back(2); |
| ranges.push_back(2); |
| grouper.SetRanges(ranges); |
| table_->SetGrouper(&grouper); |
| |
| // Toggle the sort order of the first column, shouldn't change anything. |
| table_->ToggleSortOrder(0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_TRUE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_)); |
| EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_)); |
| |
| // Sort descending, resulting: |
| // B 2 |
| // 3 |
| // A 0 |
| // 1 |
| table_->ToggleSortOrder(0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_FALSE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_)); |
| EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_)); |
| |
| // Change the entry in the 4th row to -1. The model now becomes: |
| // A 0 |
| // 1 |
| // B 2 |
| // -1 |
| // Since the first entry in the range didn't change the sort isn't impacted. |
| model_->ChangeRow(3, -1, 0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_FALSE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_)); |
| EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_)); |
| |
| // Change the entry in the 3rd row to -1. The model now becomes: |
| // A 0 |
| // 1 |
| // B -1 |
| // -1 |
| model_->ChangeRow(2, -1, 0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_FALSE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_)); |
| EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_)); |
| |
| // Toggle to ascending sort. |
| table_->ToggleSortOrder(0); |
| ASSERT_EQ(1u, table_->sort_descriptors().size()); |
| EXPECT_EQ(0, table_->sort_descriptors()[0].column_id); |
| EXPECT_TRUE(table_->sort_descriptors()[0].ascending); |
| EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_)); |
| EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_)); |
| } |
| |
| namespace { |
| |
| class TableViewObserverImpl : public TableViewObserver { |
| public: |
| TableViewObserverImpl() : selection_changed_count_(0) {} |
| |
| int GetChangedCountAndClear() { |
| const int count = selection_changed_count_; |
| selection_changed_count_ = 0; |
| return count; |
| } |
| |
| // TableViewObserver overrides: |
| virtual void OnSelectionChanged() override { |
| selection_changed_count_++; |
| } |
| |
| private: |
| int selection_changed_count_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TableViewObserverImpl); |
| }; |
| |
| } // namespace |
| |
| // Assertions around changing the selection. |
| TEST_F(TableViewTest, Selection) { |
| TableViewObserverImpl observer; |
| table_->SetObserver(&observer); |
| |
| // Initially no selection. |
| EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); |
| |
| // Select the last row. |
| table_->Select(3); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString()); |
| |
| // Change sort, shouldn't notify of change (toggle twice so that order |
| // actually changes). |
| table_->ToggleSortOrder(0); |
| table_->ToggleSortOrder(0); |
| EXPECT_EQ(0, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString()); |
| |
| // Remove the selected row, this should notify of a change and update the |
| // selection. |
| model_->RemoveRow(3); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); |
| |
| // Insert a row, since the selection in terms of the original model hasn't |
| // changed the observer is not notified. |
| model_->AddRow(0, 1, 2); |
| EXPECT_EQ(0, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString()); |
| |
| table_->SetObserver(NULL); |
| } |
| |
| // Verifies selection works by way of a gesture. |
| TEST_F(TableViewTest, SelectOnTap) { |
| // Initially no selection. |
| EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); |
| |
| TableViewObserverImpl observer; |
| table_->SetObserver(&observer); |
| |
| // Click on the first row, should select it. |
| TapOnRow(0); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString()); |
| |
| table_->SetObserver(NULL); |
| } |
| |
| // Verifies up/down correctly navigates through groups. |
| TEST_F(TableViewTest, KeyUpDown) { |
| // Configure the grouper so that there are three groups: |
| // A 0 |
| // 1 |
| // B 5 |
| // C 2 |
| // 3 |
| model_->AddRow(2, 5, 0); |
| TableGrouperImpl grouper; |
| std::vector<int> ranges; |
| ranges.push_back(2); |
| ranges.push_back(1); |
| ranges.push_back(2); |
| grouper.SetRanges(ranges); |
| table_->SetGrouper(&grouper); |
| |
| TableViewObserverImpl observer; |
| table_->SetObserver(&observer); |
| |
| // Initially no selection. |
| EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(0, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(0, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); |
| |
| // Sort the table descending by column 1, view now looks like: |
| // B 5 model: 2 |
| // C 2 3 |
| // 3 4 |
| // A 0 0 |
| // 1 1 |
| table_->ToggleSortOrder(0); |
| table_->ToggleSortOrder(0); |
| |
| EXPECT_EQ("2 3 4 0 1", GetViewToModelAsString(table_)); |
| |
| table_->Select(-1); |
| EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); |
| |
| observer.GetChangedCountAndClear(); |
| // Up with nothing selected selects the first row. |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_DOWN); |
| EXPECT_EQ(0, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_UP); |
| EXPECT_EQ(0, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString()); |
| |
| table_->SetObserver(NULL); |
| } |
| |
| // Verifies home/end do the right thing. |
| TEST_F(TableViewTest, HomeEnd) { |
| // Configure the grouper so that there are three groups: |
| // A 0 |
| // 1 |
| // B 5 |
| // C 2 |
| // 3 |
| model_->AddRow(2, 5, 0); |
| TableGrouperImpl grouper; |
| std::vector<int> ranges; |
| ranges.push_back(2); |
| ranges.push_back(1); |
| ranges.push_back(2); |
| grouper.SetRanges(ranges); |
| table_->SetGrouper(&grouper); |
| |
| TableViewObserverImpl observer; |
| table_->SetObserver(&observer); |
| |
| // Initially no selection. |
| EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_HOME); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); |
| |
| PressKey(ui::VKEY_END); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| table_->SetObserver(NULL); |
| } |
| |
| // Verifies multiple selection gestures work (control-click, shift-click ...). |
| TEST_F(TableViewTest, Multiselection) { |
| // Configure the grouper so that there are three groups: |
| // A 0 |
| // 1 |
| // B 5 |
| // C 2 |
| // 3 |
| model_->AddRow(2, 5, 0); |
| TableGrouperImpl grouper; |
| std::vector<int> ranges; |
| ranges.push_back(2); |
| ranges.push_back(1); |
| ranges.push_back(2); |
| grouper.SetRanges(ranges); |
| table_->SetGrouper(&grouper); |
| |
| // Initially no selection. |
| EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); |
| |
| TableViewObserverImpl observer; |
| table_->SetObserver(&observer); |
| |
| // Click on the first row, should select it and the second row. |
| ClickOnRow(0, 0); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString()); |
| |
| // Click on the last row, should select it and the row before it. |
| ClickOnRow(4, 0); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| // Shift click on the third row, should extend selection to it. |
| ClickOnRow(2, ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString()); |
| |
| // Control click on third row, should toggle it. |
| ClickOnRow(2, ui::EF_CONTROL_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=2 selection=3 4", SelectionStateAsString()); |
| |
| // Control-shift click on second row, should extend selection to it. |
| ClickOnRow(1, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=1 anchor=2 selection=0 1 2 3 4", SelectionStateAsString()); |
| |
| // Click on last row again. |
| ClickOnRow(4, 0); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| table_->SetObserver(NULL); |
| } |
| |
| // Verifies multiple selection gestures work when sorted. |
| TEST_F(TableViewTest, MultiselectionWithSort) { |
| // Configure the grouper so that there are three groups: |
| // A 0 |
| // 1 |
| // B 5 |
| // C 2 |
| // 3 |
| model_->AddRow(2, 5, 0); |
| TableGrouperImpl grouper; |
| std::vector<int> ranges; |
| ranges.push_back(2); |
| ranges.push_back(1); |
| ranges.push_back(2); |
| grouper.SetRanges(ranges); |
| table_->SetGrouper(&grouper); |
| |
| // Sort the table descending by column 1, view now looks like: |
| // B 5 model: 2 |
| // C 2 3 |
| // 3 4 |
| // A 0 0 |
| // 1 1 |
| table_->ToggleSortOrder(0); |
| table_->ToggleSortOrder(0); |
| |
| // Initially no selection. |
| EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); |
| |
| TableViewObserverImpl observer; |
| table_->SetObserver(&observer); |
| |
| // Click on the third row, should select it and the second row. |
| ClickOnRow(2, 0); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString()); |
| |
| // Extend selection to first row. |
| ClickOnRow(0, ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(1, observer.GetChangedCountAndClear()); |
| EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString()); |
| |
| table_->SetObserver(NULL); |
| } |
| |
| } // namespace views |