root/ui/views/controls/table/table_view_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetPaintRegion
  2. visible_col_count
  3. header
  4. AddRow
  5. RemoveRow
  6. ChangeRow
  7. RowCount
  8. GetText
  9. SetObserver
  10. CompareValues
  11. GetViewToModelAsString
  12. GetModelToViewAsString
  13. HasFocus
  14. SetUp
  15. ClickOnRow
  16. TapOnRow
  17. SelectionStateAsString
  18. PressKey
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. TEST_F
  23. TEST_F
  24. TEST_F
  25. SetRanges
  26. GetGroupRange
  27. TEST_F
  28. GetChangedCountAndClear
  29. OnSelectionChanged
  30. TEST_F
  31. TEST_F
  32. TEST_F
  33. TEST_F
  34. TEST_F
  35. TEST_F

// 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(ui::ET_GESTURE_TAP, 0, y, 0, base::TimeDelta(),
                         event_details, 1);
    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, 0, false);
    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.
  const ui::GestureEventDetails event_details(ui::ET_GESTURE_SCROLL_BEGIN,
                                              .0f, .0f);
  ui::GestureEvent scroll_begin(ui::ET_GESTURE_SCROLL_BEGIN, x, 0, 0,
                                base::TimeDelta(), event_details, 1);
  helper_->header()->OnGestureEvent(&scroll_begin);
  ui::GestureEvent scroll_update(ui::ET_GESTURE_SCROLL_UPDATE, x - 1, 0,
                                 0, base::TimeDelta(), event_details, 1);
  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

/* [<][>][^][v][top][bottom][index][help] */