blob: e592f2e2c6804a81bd3b6347173aa18b2ca773ba [file] [log] [blame]
// 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,
.0f, .0f);
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, .0f, .0f));
helper_->header()->OnGestureEvent(&scroll_begin);
ui::GestureEvent scroll_update(
x - 1,
0,
0,
base::TimeDelta(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, .0f, .0f));
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