// 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 <stddef.h>

#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.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"
#include "ui/views/test/focus_manager_test.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.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_; }

  void SetSelectionModel(const ui::ListSelectionModel& new_selection) {
    table_->SetSelectionModel(new_selection);
  }

 private:
  TableView* table_;

  DISALLOW_COPY_AND_ASSIGN(TableViewTestHelper);
};

namespace {

#if defined(OS_MACOSX)
constexpr int kCtrlOrCmdMask = ui::EF_COMMAND_DOWN;
#else
constexpr int kCtrlOrCmdMask = ui::EF_CONTROL_DOWN;
#endif

// 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);

  // Reorders rows in the model.
  void MoveRows(int row_from, int length, int row_to);

  // ui::TableModel:
  int RowCount() override;
  base::string16 GetText(int row, int column_id) override;
  void SetObserver(ui::TableModelObserver* observer) override;
  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);
}

void TestTableModel2::MoveRows(int row_from, int length, int row_to) {
  DCHECK_GT(length, 0);
  DCHECK_GE(row_from, 0);
  DCHECK_LE(row_from + length, static_cast<int>(rows_.size()));
  DCHECK_GE(row_to, 0);
  DCHECK_LE(row_to + length, static_cast<int>(rows_.size()));

  auto old_start = rows_.begin() + row_from;
  std::vector<std::vector<int>> temp(old_start, old_start + length);
  rows_.erase(old_start, old_start + length);
  rows_.insert(rows_.begin() + row_to, temp.begin(), temp.end());
  if (observer_)
    observer_->OnItemsMoved(row_from, length, row_to);
}

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;
}

// Formats the whole table as a string, like: "[a, b, c], [d, e, f]". Rows
// scrolled out of view are included; hidden columns are excluded.
std::string GetRowsInViewOrderAsString(TableView* table) {
  std::string result;
  for (int i = 0; i < table->RowCount(); ++i) {
    if (i != 0)
      result += ", ";  // Comma between each row.

    // Format row |i| like this: "[value1, value2, value3]"
    result += "[";
    for (size_t j = 0; j < table->visible_columns().size(); ++j) {
      const ui::TableColumn& column = table->visible_columns()[j].column;
      if (j != 0)
        result += ", ";  // Comma between each value in the row.

      result += base::UTF16ToUTF8(
          table->model()->GetText(table->ViewToModel(i), column.id));
    }
    result += "]";
  }
  return result;
}

}  // namespace

class TableViewTest : public ViewsTestBase {
 public:
  TableViewTest() : table_(NULL) {}

  void SetUp() override {
    ViewsTestBase::SetUp();

    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 TableView(model_.get(), columns, TEXT_ONLY, false);
    View* parent = table_->CreateParentIfNecessary();
    parent->SetBounds(0, 0, 10000, 10000);
    parent->Layout();
    helper_.reset(new TableViewTestHelper(table_));

    widget_ = std::make_unique<Widget>();
    Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    params.bounds = gfx::Rect(0, 0, 650, 650);
    params.delegate = GetWidgetDelegate(widget_.get());
    widget_->Init(params);
    widget_->GetContentsView()->AddChildView(parent);
    widget_->Show();
  }

  void TearDown() override {
    widget_.reset();
    ViewsTestBase::TearDown();
  }

  void ClickOnRow(int row, int flags) {
    ui::test::EventGenerator generator(widget_->GetNativeWindow());
    generator.set_assume_window_at_origin(false);
    generator.set_flags(flags);
    generator.set_current_location(GetPointForRow(row));
    generator.PressLeftButton();
  }

  void TapOnRow(int row) {
    ui::test::EventGenerator generator(widget_->GetNativeWindow());
    generator.GestureTapAt(GetPointForRow(row));
  }

  // 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::test::EventGenerator generator(widget_->GetNativeWindow());
    generator.PressKey(code, ui::EF_NONE);
  }

 protected:
  virtual WidgetDelegate* GetWidgetDelegate(Widget* widget) { return nullptr; }

  std::unique_ptr<TestTableModel2> model_;

  // Owned by |parent_|.
  TableView* table_;

  std::unique_ptr<TableViewTestHelper> helper_;

  std::unique_ptr<Widget> widget_;

 private:
  gfx::Point GetPointForRow(int row) {
    const int y = (row + 0.5) * table_->row_height();
    return table_->GetBoundsInScreen().origin() + gfx::Vector2d(5, y);
  }

  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::EventTimeForNow(),
                               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::EventTimeForNow(),
                               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::TimeTicks(),
      ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
  helper_->header()->OnGestureEvent(&scroll_begin);
  ui::GestureEvent scroll_update(
      x - 1, 0, 0, base::TimeTicks(),
      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) {
  // Initial ordering.
  EXPECT_TRUE(table_->sort_descriptors().empty());
  EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
  EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
  EXPECT_EQ("[0, 1], [1, 1], [2, 2], [3, 0]",
            GetRowsInViewOrderAsString(table_));

  // 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_));
  EXPECT_EQ("[0, 1], [1, 1], [2, 2], [3, 0]",
            GetRowsInViewOrderAsString(table_));

  // Toggle 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_));
  EXPECT_EQ("[3, 0], [2, 2], [1, 1], [0, 1]",
            GetRowsInViewOrderAsString(table_));

  // Change the [3, 0] cell to [-1, 0]. This should move it to the back of
  // the current sort order.
  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_));
  EXPECT_EQ("[2, 2], [1, 1], [0, 1], [-1, 0]",
            GetRowsInViewOrderAsString(table_));

  // Toggle the sort again, to clear the sort and restore the model ordering.
  table_->ToggleSortOrder(0);
  EXPECT_TRUE(table_->sort_descriptors().empty());
  EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
  EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
  EXPECT_EQ("[0, 1], [1, 1], [2, 2], [-1, 0]",
            GetRowsInViewOrderAsString(table_));

  // Toggle the 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_));
  EXPECT_EQ("[-1, 0], [0, 1], [1, 1], [2, 2]",
            GetRowsInViewOrderAsString(table_));

  // Add a row that's second in the model order, but last in the active sort
  // order.
  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_));
  EXPECT_EQ("[-1, 0], [0, 1], [1, 1], [2, 2], [3, 4]",
            GetRowsInViewOrderAsString(table_));

  // Add a row that's last in the model order but second in the the active sort
  // order.
  model_->AddRow(5, -1, 20);
  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 5 0 2 3 1", GetViewToModelAsString(table_));
  EXPECT_EQ("2 5 3 4 0 1", GetModelToViewAsString(table_));
  EXPECT_EQ("[-1, 0], [-1, 20], [0, 1], [1, 1], [2, 2], [3, 4]",
            GetRowsInViewOrderAsString(table_));

  // Click the first column again, then click the second column. This should
  // yield an ordering of second column ascending, with the first column
  // descending as a tiebreaker.
  table_->ToggleSortOrder(0);
  table_->ToggleSortOrder(1);
  ASSERT_EQ(2u, table_->sort_descriptors().size());
  EXPECT_EQ(1, table_->sort_descriptors()[0].column_id);
  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
  EXPECT_EQ(0, table_->sort_descriptors()[1].column_id);
  EXPECT_FALSE(table_->sort_descriptors()[1].ascending);
  EXPECT_EQ("4 2 0 3 1 5", GetViewToModelAsString(table_));
  EXPECT_EQ("2 4 1 3 0 5", GetModelToViewAsString(table_));
  EXPECT_EQ("[-1, 0], [1, 1], [0, 1], [2, 2], [3, 4], [-1, 20]",
            GetRowsInViewOrderAsString(table_));

  // Toggle the current column to change from ascending to descending. This
  // should result in an almost-reversal of the previous order, except for the
  // two rows with the same value for the second column.
  table_->ToggleSortOrder(1);
  ASSERT_EQ(2u, table_->sort_descriptors().size());
  EXPECT_EQ(1, table_->sort_descriptors()[0].column_id);
  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
  EXPECT_EQ(0, table_->sort_descriptors()[1].column_id);
  EXPECT_FALSE(table_->sort_descriptors()[1].ascending);
  EXPECT_EQ("5 1 3 2 0 4", GetViewToModelAsString(table_));
  EXPECT_EQ("4 1 3 2 5 0", GetModelToViewAsString(table_));
  EXPECT_EQ("[-1, 20], [3, 4], [2, 2], [1, 1], [0, 1], [-1, 0]",
            GetRowsInViewOrderAsString(table_));

  // Delete the [0, 1] row from the model. It's at model index zero.
  model_->RemoveRow(0);
  ASSERT_EQ(2u, table_->sort_descriptors().size());
  EXPECT_EQ(1, table_->sort_descriptors()[0].column_id);
  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
  EXPECT_EQ(0, table_->sort_descriptors()[1].column_id);
  EXPECT_FALSE(table_->sort_descriptors()[1].ascending);
  EXPECT_EQ("4 0 2 1 3", GetViewToModelAsString(table_));
  EXPECT_EQ("1 3 2 4 0", GetModelToViewAsString(table_));
  EXPECT_EQ("[-1, 20], [3, 4], [2, 2], [1, 1], [-1, 0]",
            GetRowsInViewOrderAsString(table_));

  // Toggle the current sort column again. This should clear both the primary
  // and secondary sort descriptor.
  table_->ToggleSortOrder(1);
  EXPECT_TRUE(table_->sort_descriptors().empty());
  EXPECT_EQ("0 1 2 3 4", GetViewToModelAsString(table_));
  EXPECT_EQ("0 1 2 3 4", GetModelToViewAsString(table_));
  EXPECT_EQ("[3, 4], [1, 1], [2, 2], [-1, 0], [-1, 20]",
            GetRowsInViewOrderAsString(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::EventTimeForNow(),
                               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::EventTimeForNow(),
                               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:
  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 clear the sort.
  table_->ToggleSortOrder(0);
  EXPECT_TRUE(table_->sort_descriptors().empty());
  EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
  EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));

  // Toggle again to effect an 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:
  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_->set_observer(&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());

  // Swap the first two rows. This shouldn't affect selection.
  model_->MoveRows(0, 1, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());

  // Move the first row to after the selection. This will change the selection
  // state.
  model_->MoveRows(0, 1, 3);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());

  // Move the first two rows to be after the selection. This will change the
  // selection state.
  model_->MoveRows(0, 2, 2);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  // Move some rows after the selection.
  model_->MoveRows(2, 2, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  // Move the selection itself.
  model_->MoveRows(0, 1, 3);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());

  // Move-left a range that ends at the selection
  model_->MoveRows(2, 2, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());

  // Move-right a range that ends at the selection
  model_->MoveRows(1, 2, 2);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());

  // Add a row at the end.
  model_->AddRow(4, 7, 9);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());

  // Move-left a range that includes the selection.
  model_->MoveRows(2, 3, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());

  // Move-right a range that includes the selection.
  model_->MoveRows(0, 4, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());

  table_->set_observer(NULL);
}

TEST_F(TableViewTest, RemoveUnselectedRows) {
  TableViewObserverImpl observer;
  table_->set_observer(&observer);

  // Select a middle row.
  table_->Select(2);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());

  // Remove the last row. This should notify of a change.
  model_->RemoveRow(3);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());

  // Remove the first row. This should also notify of a change.
  model_->RemoveRow(0);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
}

// 0 1 2 3:
// select 3 -> 0 1 2 [3]
// remove 3 -> 0 1 2 (none selected)
// select 1 -> 0 [1] 2
// remove 1 -> 0 1 (none selected)
// select 0 -> [0] 1
// remove 0 -> 0 (none selected)
TEST_F(TableViewTest, SelectionNoSelectOnRemove) {
  TableViewObserverImpl observer;
  table_->set_observer(&observer);
  table_->set_select_on_remove(false);

  // Initially no selection.
  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());

  // Select row 3.
  table_->Select(3);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());

  // Remove the selected row, this should notify of a change and since the
  // select_on_remove_ is set false, and the removed item is the previously
  // selected item, so no item is selected.
  model_->RemoveRow(3);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());

  // Select row 1.
  table_->Select(1);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  // Remove the selected row.
  model_->RemoveRow(1);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());

  // Select row 0.
  table_->Select(0);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  // Remove the selected row.
  model_->RemoveRow(0);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());

  table_->set_observer(nullptr);
}

// No touch on desktop Mac. Tracked in http://crbug.com/445520.
#if !defined(OS_MACOSX)
// 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_->set_observer(&observer);

  // Tap on the first row, should select it and focus the table.
  EXPECT_FALSE(table_->HasFocus());
  TapOnRow(0);
  EXPECT_TRUE(table_->HasFocus());
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  table_->set_observer(NULL);
}
#endif

// 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_->set_observer(&observer);
  table_->RequestFocus();

  // 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_->set_observer(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_->set_observer(&observer);
  table_->RequestFocus();

  // 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());

  PressKey(ui::VKEY_HOME);
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());

  table_->set_observer(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_->set_observer(&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, kCtrlOrCmdMask);
  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, kCtrlOrCmdMask | 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_->set_observer(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_->set_observer(&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());
}

TEST_F(TableViewTest, MoveRowsWithMultipleSelection) {
  model_->AddRow(3, 77, 0);

  // Hide column 1.
  table_->SetColumnVisibility(1, false);

  TableViewObserverImpl observer;
  table_->set_observer(&observer);

  // Select three rows.
  ClickOnRow(2, 0);
  ClickOnRow(4, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(2, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=4 anchor=2 selection=2 3 4", SelectionStateAsString());
  EXPECT_EQ("[0], [1], [2], [77], [3]", GetRowsInViewOrderAsString(table_));

  // Move the unselected rows to the middle of the current selection. None of
  // the move operations should affect the view order.
  model_->MoveRows(0, 2, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=4 anchor=0 selection=0 3 4", SelectionStateAsString());
  EXPECT_EQ("[2], [0], [1], [77], [3]", GetRowsInViewOrderAsString(table_));

  // Move the unselected rows to the end of the current selection.
  model_->MoveRows(1, 2, 3);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=2 anchor=0 selection=0 1 2", SelectionStateAsString());
  EXPECT_EQ("[2], [77], [3], [0], [1]", GetRowsInViewOrderAsString(table_));

  // Move the unselected rows back to the middle of the selection.
  model_->MoveRows(3, 2, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=4 anchor=0 selection=0 3 4", SelectionStateAsString());
  EXPECT_EQ("[2], [0], [1], [77], [3]", GetRowsInViewOrderAsString(table_));

  // Swap the unselected rows.
  model_->MoveRows(1, 1, 2);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=4 anchor=0 selection=0 3 4", SelectionStateAsString());
  EXPECT_EQ("[2], [1], [0], [77], [3]", GetRowsInViewOrderAsString(table_));

  // Move the second unselected row to be between two selected rows.
  model_->MoveRows(2, 1, 3);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=4 anchor=0 selection=0 2 4", SelectionStateAsString());
  EXPECT_EQ("[2], [1], [77], [0], [3]", GetRowsInViewOrderAsString(table_));

  // Move the three middle rows to the beginning, including one selected row.
  model_->MoveRows(1, 3, 0);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=4 anchor=3 selection=1 3 4", SelectionStateAsString());
  EXPECT_EQ("[1], [77], [0], [2], [3]", GetRowsInViewOrderAsString(table_));

  table_->set_observer(NULL);
}

TEST_F(TableViewTest, MoveRowsWithMultipleSelectionAndSort) {
  model_->AddRow(3, 77, 0);

  // Sort ascending by column 0, and hide column 1. The view order should not
  // change during this test.
  table_->ToggleSortOrder(0);
  table_->SetColumnVisibility(1, false);
  const char* kViewOrder = "[0], [1], [2], [3], [77]";
  EXPECT_EQ(kViewOrder, GetRowsInViewOrderAsString(table_));

  TableViewObserverImpl observer;
  table_->set_observer(&observer);

  // Select three rows.
  ClickOnRow(2, 0);
  ClickOnRow(4, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(2, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=2 selection=2 3 4", SelectionStateAsString());

  // Move the unselected rows to the middle of the current selection. None of
  // the move operations should affect the view order.
  model_->MoveRows(0, 2, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=0 selection=0 3 4", SelectionStateAsString());
  EXPECT_EQ(kViewOrder, GetRowsInViewOrderAsString(table_));

  // Move the unselected rows to the end of the current selection.
  model_->MoveRows(1, 2, 3);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=0 selection=0 1 2", SelectionStateAsString());
  EXPECT_EQ(kViewOrder, GetRowsInViewOrderAsString(table_));

  // Move the unselected rows back to the middle of the selection.
  model_->MoveRows(3, 2, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=0 selection=0 3 4", SelectionStateAsString());
  EXPECT_EQ(kViewOrder, GetRowsInViewOrderAsString(table_));

  // Swap the unselected rows.
  model_->MoveRows(1, 1, 2);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=0 selection=0 3 4", SelectionStateAsString());
  EXPECT_EQ(kViewOrder, GetRowsInViewOrderAsString(table_));

  // Swap the unselected rows again.
  model_->MoveRows(2, 1, 1);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=0 selection=0 3 4", SelectionStateAsString());
  EXPECT_EQ(kViewOrder, GetRowsInViewOrderAsString(table_));

  // Move the unselected rows back to the beginning.
  model_->MoveRows(1, 2, 0);
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=3 anchor=2 selection=2 3 4", SelectionStateAsString());
  EXPECT_EQ(kViewOrder, GetRowsInViewOrderAsString(table_));

  table_->set_observer(NULL);
}

// Verifies we don't crash after removing the selected row when there is
// sorting and the anchor/active index also match the selected row.
TEST_F(TableViewTest, FocusAfterRemovingAnchor) {
  table_->ToggleSortOrder(0);

  ui::ListSelectionModel new_selection;
  new_selection.AddIndexToSelection(0);
  new_selection.AddIndexToSelection(1);
  new_selection.set_active(0);
  new_selection.set_anchor(0);
  helper_->SetSelectionModel(new_selection);
  model_->RemoveRow(0);
  table_->RequestFocus();
}

TEST_F(TableViewTest, RemovingInvalidRowIsNoOp) {
  table_->Select(3);
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
  table_->OnItemsRemoved(4, 1);
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
  table_->OnItemsRemoved(2, 0);
  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
}

namespace {

class RemoveFocusChangeListenerDelegate : public WidgetDelegate {
 public:
  explicit RemoveFocusChangeListenerDelegate(Widget* widget)
      : widget_(widget), listener_(nullptr) {}
  ~RemoveFocusChangeListenerDelegate() override {}

  // WidgetDelegate overrides:
  void DeleteDelegate() override;
  Widget* GetWidget() override { return widget_; }
  const Widget* GetWidget() const override { return widget_; }

  void SetFocusChangeListener(FocusChangeListener* listener);

 private:
  Widget* widget_;
  FocusChangeListener* listener_;

  DISALLOW_COPY_AND_ASSIGN(RemoveFocusChangeListenerDelegate);
};

void RemoveFocusChangeListenerDelegate::DeleteDelegate() {
  widget_->GetFocusManager()->RemoveFocusChangeListener(listener_);
}

void RemoveFocusChangeListenerDelegate::SetFocusChangeListener(
    FocusChangeListener* listener) {
  listener_ = listener;
}

}  // namespace

class TableViewFocusTest : public TableViewTest {
 public:
  TableViewFocusTest() = default;

 protected:
  WidgetDelegate* GetWidgetDelegate(Widget* widget) override;

  RemoveFocusChangeListenerDelegate* GetFocusChangeListenerDelegate() {
    return delegate_.get();
  }

 private:
  std::unique_ptr<RemoveFocusChangeListenerDelegate> delegate_;

  DISALLOW_COPY_AND_ASSIGN(TableViewFocusTest);
};

WidgetDelegate* TableViewFocusTest::GetWidgetDelegate(Widget* widget) {
  delegate_ = std::make_unique<RemoveFocusChangeListenerDelegate>(widget);
  return delegate_.get();
}

// Verifies that the active focus is cleared when the widget is destroyed.
// In MD mode, if that doesn't happen a DCHECK in View::DoRemoveChildView(...)
// will trigger due to an attempt to modify the child view list while iterating.
TEST_F(TableViewFocusTest, FocusClearedDuringWidgetDestruction) {
  TestFocusChangeListener listener;
  GetFocusChangeListenerDelegate()->SetFocusChangeListener(&listener);

  widget_->GetFocusManager()->AddFocusChangeListener(&listener);
  table_->RequestFocus();

  ASSERT_EQ(1u, listener.focus_changes().size());
  EXPECT_EQ(listener.focus_changes()[0], ViewPair(nullptr, table_));
  listener.ClearFocusChanges();

  // Now destroy the widget. This should *not* cause a DCHECK in
  // View::DoRemoveChildView(...).
  widget_.reset();
  ASSERT_EQ(1u, listener.focus_changes().size());
  EXPECT_EQ(listener.focus_changes()[0], ViewPair(table_, nullptr));
}

}  // namespace views
