text:
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::NumberToString(region.min_row) + " " +
           base::NumberToString(region.max_row) +
           " cols=" + base::NumberToString(region.min_column) + " " +
           base::NumberToString(region.max_column);
  }

  size_t visible_col_count() { return table_->visible_columns().size(); }

  int GetActiveVisibleColumnIndex() {
    return table_->GetActiveVisibleColumnIndex();
  }

  TableHeader* header() { return table_->header_; }

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

  const gfx::FontList& font_list() { return table_->font_list_; }

  AXVirtualView* GetVirtualAccessibilityRow(int row) {
    return table_->GetVirtualAccessibilityRow(row);
  }

  AXVirtualView* GetVirtualAccessibilityCell(int row,
                                             int visible_column_index) {
    return table_->GetVirtualAccessibilityCell(row, visible_column_index);
  }

  std::vector<std::vector<gfx::Rect>> GenerateExpectedBounds() {
    // Generates the expected bounds for |table_|'s rows and cells. Each vector
    // represents a row. The first entry in each child vector is the bounds for
    // the entire row. The following entries in that vector are the bounds for
    // each individual cell contained in that row.
    auto expected_bounds = std::vector<std::vector<gfx::Rect>>();

    // Generate the bounds for the header row and cells.
    auto header_row = std::vector<gfx::Rect>();
    gfx::Rect header_row_bounds =
        table_->CalculateHeaderRowAccessibilityBounds();
    View::ConvertRectToScreen(table_, &header_row_bounds);
    header_row.push_back(header_row_bounds);
    for (size_t column_index = 0; column_index < visible_col_count();
         column_index++) {
      gfx::Rect header_cell_bounds =
          table_->CalculateHeaderCellAccessibilityBounds(column_index);
      View::ConvertRectToScreen(table_, &header_cell_bounds);
      header_row.push_back(header_cell_bounds);
    }
    expected_bounds.push_back(header_row);

    // Generate the bounds for the table rows and cells.
    for (int row_index = 0; row_index < table_->GetRowCount(); row_index++) {
      auto table_row = std::vector<gfx::Rect>();
      gfx::Rect table_row_bounds =
          table_->CalculateTableRowAccessibilityBounds(row_index);
      View::ConvertRectToScreen(table_, &table_row_bounds);
      table_row.push_back(table_row_bounds);
      for (size_t column_index = 0; column_index < visible_col_count();
           column_index++) {
        gfx::Rect table_cell_bounds =
            table_->CalculateTableCellAccessibilityBounds(row_index,
                                                          column_index);
        View::ConvertRectToScreen(table_, &table_cell_bounds);
        table_row.push_back(table_cell_bounds);
      }
      expected_bounds.push_back(table_row);
    }

    return expected_bounds;
  }

 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;
  base::string16 GetTooltip(int row) override;
  void SetObserver(ui::TableModelObserver* observer) override;
  int CompareValues(int row1, int row2, int column_id) override;

 private:
  ui::TableModelObserver* observer_ = nullptr;

  // The data.
  std::vector<std::vector<int>> rows_;

  DISALLOW_COPY_AND_ASSIGN(TestTableModel2);
};

TestTableModel2::TestTableModel2() {
  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::NumberToString16(rows_[row][column_id]);
}

base::string16 TestTableModel2::GetTooltip(int row) {
  return base::ASCIIToUTF16("Tooltip") + base::NumberToString16(row);
}

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->GetRowCount(); ++i) {
    if (i != 0)
      result += " ";
    result += base::NumberToString(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->GetRowCount(); ++i) {
    if (i != 0)
      result += " ";
    result += base::NumberToString(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->GetRowCount(); ++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->GetVisibleColumn(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;
}

bool PressLeftMouseAt(views::View* target, const gfx::Point& point) {
  const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, point, point,
                               ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                               ui::EF_LEFT_MOUSE_BUTTON);
  return target->OnMousePressed(pressed);
}

void ReleaseLeftMouseAt(views::View* target, const gfx::Point& point) {
  const ui::MouseEvent release(ui::ET_MOUSE_RELEASED, point, point,
                               ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                               ui::EF_LEFT_MOUSE_BUTTON);
  target->OnMouseReleased(release);
}

bool DragLeftMouseTo(views::View* target, const gfx::Point& point) {
  const ui::MouseEvent dragged(ui::ET_MOUSE_DRAGGED, point, point,
                               ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                               0);
  return target->OnMouseDragged(dragged);
}

}  // namespace

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

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

    model_ = std::make_unique<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;
    auto table =
        std::make_unique<TableView>(model_.get(), columns, TEXT_ONLY, false);
    table_ = table.get();
    auto scroll_view = TableView::CreateScrollViewWithTable(std::move(table));
    scroll_view->SetBounds(0, 0, 10000, 10000);
    scroll_view->Layout();
    helper_ = std::make_unique<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(std::move(params));
    widget_->GetContentsView()->AddChildView(std::move(scroll_view));
    widget_->Show();
  }

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

  void ClickOnRow(int row, int flags) {
    ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
    generator.set_assume_window_at_origin(false);
    generator.set_flags(flags);
    generator.set_current_screen_location(GetPointForRow(row));
    generator.PressLeftButton();
  }

  void TapOnRow(int row) {
    ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
    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::NumberToString(model.active()) +
                         " anchor=" + base::NumberToString(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::NumberToString(selection[i]);
    }
    return result;
  }

  void PressKey(ui::KeyboardCode code) { PressKey(code, ui::EF_NONE); }

  void PressKey(ui::KeyboardCode code, int flags) {
    ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
    generator.PressKey(code, flags);
  }

  // Helper function for comparing the bounds of |table_|'s virtual
  // accessibility child rows and cells with a set of expected bounds.
  void VerifyTableAccChildrenBounds(
      const ViewAccessibility& view_accessibility,
      const std::vector<std::vector<gfx::Rect>>& expected_bounds) {
    auto& virtual_children = view_accessibility.virtual_children();
    EXPECT_EQ(virtual_children.size(), expected_bounds.size());
    EXPECT_EQ((size_t)(table_->GetRowCount()) + 1U, expected_bounds.size());

    for (size_t row_index = 0; row_index < virtual_children.size();
         row_index++) {
      const auto& row = virtual_children[row_index];
      ASSERT_TRUE(row);
      const ui::AXNodeData& row_data = row->GetData();
      EXPECT_EQ(ax::mojom::Role::kRow, row_data.role);

      ui::AXOffscreenResult offscreen_result = ui::AXOffscreenResult();
      gfx::Rect row_custom_bounds = row->GetBoundsRect(
          ui::AXCoordinateSystem::kScreenDIPs,
          ui::AXClippingBehavior::kUnclipped, &offscreen_result);
      EXPECT_EQ(row_custom_bounds, expected_bounds[row_index][0]);

      EXPECT_EQ(row->children().size(), expected_bounds[row_index].size() - 1U);
      EXPECT_EQ(row->children().size(), helper_->visible_col_count());
      for (size_t cell_index = 0; cell_index < row->children().size();
           cell_index++) {
        const auto& cell = row->children()[cell_index];
        ASSERT_TRUE(cell);
        const ui::AXNodeData& cell_data = cell->GetData();

        if (row_index == 0)
          EXPECT_EQ(ax::mojom::Role::kColumnHeader, cell_data.role);
        else
          EXPECT_EQ(ax::mojom::Role::kCell, cell_data.role);

        // Add 1 to get the cell's index into |expected_bounds| since the first
        // entry is the row's bounds.
        const int expected_bounds_index = cell_index + 1;
        gfx::Rect cell_custom_bounds = cell->GetBoundsRect(
            ui::AXCoordinateSystem::kScreenDIPs,
            ui::AXClippingBehavior::kUnclipped, &offscreen_result);
        EXPECT_EQ(cell_custom_bounds,
                  expected_bounds[row_index][expected_bounds_index]);
      }
    }
  }

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

  std::unique_ptr<TestTableModel2> model_;

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

  std::unique_ptr<TableViewTestHelper> helper_;

  std::unique_ptr<Widget> widget_;

 private:
  gfx::Point GetPointForRow(int row) {
    const int y = (row + 0.5) * table_->GetRowHeight();
    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())));
}

TEST_F(TableViewTest, UpdateVirtualAccessibilityChildren) {
  const ViewAccessibility& view_accessibility = table_->GetViewAccessibility();
  ui::AXNodeData data;
  view_accessibility.GetAccessibleNodeData(&data);
  EXPECT_EQ(ax::mojom::Role::kListGrid, data.role);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));
  EXPECT_EQ(ax::mojom::Restriction::kReadOnly, data.GetRestriction());
  EXPECT_EQ(table_->GetRowCount(),
            static_cast<int>(
                data.GetIntAttribute(ax::mojom::IntAttribute::kTableRowCount)));
  EXPECT_EQ(helper_->visible_col_count(),
            static_cast<size_t>(data.GetIntAttribute(
                ax::mojom::IntAttribute::kTableColumnCount)));

  // The header takes up another row.
  ASSERT_EQ(size_t{table_->GetRowCount() + 1},
            view_accessibility.virtual_children().size());
  const auto& header = view_accessibility.virtual_children().front();
  ASSERT_TRUE(header);
  EXPECT_EQ(ax::mojom::Role::kRow, header->GetData().role);

  ASSERT_EQ(helper_->visible_col_count(), header->children().size());
  int j = 0;
  for (const auto& header_cell : header->children()) {
    ASSERT_TRUE(header_cell);
    const ui::AXNodeData& header_cell_data = header_cell->GetData();
    EXPECT_EQ(ax::mojom::Role::kColumnHeader, header_cell_data.role);
    EXPECT_EQ(j++, header_cell_data.GetIntAttribute(
                       ax::mojom::IntAttribute::kTableCellColumnIndex));
  }

  int i = 0;
  for (auto child_iter = view_accessibility.virtual_children().begin() + 1;
       i < table_->GetRowCount(); ++child_iter, ++i) {
    const auto& row = *child_iter;
    ASSERT_TRUE(row);
    const ui::AXNodeData& row_data = row->GetData();
    EXPECT_EQ(ax::mojom::Role::kRow, row_data.role);
    EXPECT_EQ(
        i, row_data.GetIntAttribute(ax::mojom::IntAttribute::kTableRowIndex));
    ASSERT_FALSE(row_data.HasState(ax::mojom::State::kInvisible));

    ASSERT_EQ(helper_->visible_col_count(), row->children().size());
    j = 0;
    for (const auto& cell : row->children()) {
      ASSERT_TRUE(cell);
      const ui::AXNodeData& cell_data = cell->GetData();
      EXPECT_EQ(ax::mojom::Role::kCell, cell_data.role);
      EXPECT_EQ(i, cell_data.GetIntAttribute(
                       ax::mojom::IntAttribute::kTableCellRowIndex));
      EXPECT_EQ(j++, cell_data.GetIntAttribute(
                         ax::mojom::IntAttribute::kTableCellColumnIndex));
      ASSERT_FALSE(cell_data.HasState(ax::mojom::State::kInvisible));
    }
  }
}

// Verifies the bounding rect of each virtual accessibility child of the
// TableView (rows and cells) is updated appropriately as the table changes. For
// example, verifies that if a column is resized or hidden, the bounds are
// updated.
TEST_F(TableViewTest, UpdateVirtualAccessibilityChildrenBounds) {
  // Verify the bounds are updated correctly when the TableView and its widget
  // have been shown. Initially some widths would be 0 until the TableView's
  // bounds are fully set up, so make sure the virtual children bounds have been
  // updated and now match the expected bounds.
  auto expected_bounds = helper_->GenerateExpectedBounds();
  VerifyTableAccChildrenBounds(table_->GetViewAccessibility(), expected_bounds);
}

TEST_F(TableViewTest, UpdateVirtualAccessibilityChildrenBoundsWithResize) {
  // Resize the first column 10 pixels smaller and check the bounds are updated.
  int x = table_->GetVisibleColumn(0).width;
  PressLeftMouseAt(helper_->header(), gfx::Point(x, 0));
  DragLeftMouseTo(helper_->header(), gfx::Point(x - 10, 0));

  auto expected_bounds_after_resize = helper_->GenerateExpectedBounds();
  VerifyTableAccChildrenBounds(table_->GetViewAccessibility(),
                               expected_bounds_after_resize);
}

TEST_F(TableViewTest, UpdateVirtualAccessibilityChildrenBoundsHideColumn) {
  // Hide 1 column and check the bounds are updated.
  table_->SetColumnVisibility(1, false);
  auto expected_bounds_after_hiding = helper_->GenerateExpectedBounds();
  VerifyTableAccChildrenBounds(table_->GetViewAccessibility(),
                               expected_bounds_after_hiding);
}

TEST_F(TableViewTest, GetVirtualAccessibilityRow) {
  for (int i = 0; i < table_->GetRowCount(); ++i) {
    const AXVirtualView* row = helper_->GetVirtualAccessibilityRow(i);
    ASSERT_TRUE(row);
    const ui::AXNodeData& row_data = row->GetData();
    EXPECT_EQ(ax::mojom::Role::kRow, row_data.role);
    EXPECT_EQ(i, static_cast<int>(row_data.GetIntAttribute(
                     ax::mojom::IntAttribute::kTableRowIndex)));
  }
}

TEST_F(TableViewTest, GetVirtualAccessibilityCell) {
  for (int i = 0; i < table_->GetRowCount(); ++i) {
    for (int j = 0; j < static_cast<int>(helper_->visible_col_count()); ++j) {
      const AXVirtualView* cell = helper_->GetVirtualAccessibilityCell(i, j);
      ASSERT_TRUE(cell);
      const ui::AXNodeData& cell_data = cell->GetData();
      EXPECT_EQ(ax::mojom::Role::kCell, cell_data.role);
      EXPECT_EQ(i, static_cast<int>(cell_data.GetIntAttribute(
                       ax::mojom::IntAttribute::kTableCellRowIndex)));
      EXPECT_EQ(j, static_cast<int>(cell_data.GetIntAttribute(
                       ax::mojom::IntAttribute::kTableCellColumnIndex)));
    }
  }
}

// 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_->GetVisibleColumn(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_->GetVisibleColumn(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_->GetVisibleColumn(0).column.id);
  EXPECT_EQ(0, table_->GetVisibleColumn(1).column.id);
  EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds()));
}

// Verifies resizing a column using the mouse works.
TEST_F(TableViewTest, Resize) {
  const int x = table_->GetVisibleColumn(0).width;
  EXPECT_NE(0, x);
  // Drag the mouse 1 pixel to the left.
  PressLeftMouseAt(helper_->header(), gfx::Point(x, 0));
  DragLeftMouseTo(helper_->header(), gfx::Point(x - 1, 0));

  // This should shrink the first column and pull the second column in.
  EXPECT_EQ(x - 1, table_->GetVisibleColumn(0).width);
  EXPECT_EQ(x - 1, table_->GetVisibleColumn(1).x);
}

// Verifies resizing a column works with a gesture.
TEST_F(TableViewTest, ResizeViaGesture) {
  const int x = table_->GetVisibleColumn(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_->GetVisibleColumn(0).width);
  EXPECT_EQ(x - 1, table_->GetVisibleColumn(1).x);
}

// Verifies resizing a column works with the keyboard.
// The resize keyboard amount is 5 pixels.
TEST_F(TableViewTest, ResizeViaKeyboard) {
  if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell)
    return;

  table_->RequestFocus();
  const int x = table_->GetVisibleColumn(0).width;
  EXPECT_NE(0, x);

  // Table starts off with no visible column being active.
  ASSERT_EQ(-1, helper_->GetActiveVisibleColumnIndex());

  ui::ListSelectionModel new_selection;
  new_selection.SetSelectedIndex(1);
  helper_->SetSelectionModel(new_selection);
  ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex());

  PressKey(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN);
  // This should shrink the first column and pull the second column in.
  EXPECT_EQ(x - 5, table_->GetVisibleColumn(0).width);
  EXPECT_EQ(x - 5, table_->GetVisibleColumn(1).x);

  PressKey(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN);
  // This should restore the columns to their original sizes.
  EXPECT_EQ(x, table_->GetVisibleColumn(0).width);
  EXPECT_EQ(x, table_->GetVisibleColumn(1).x);

  PressKey(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN);
  // This should expand the first column and push the second column out.
  EXPECT_EQ(x + 5, table_->GetVisibleColumn(0).width);
  EXPECT_EQ(x + 5, table_->GetVisibleColumn(1).x);
}

// Verifies resizing a column won't reduce the column width below the width of
// the column's title text.
TEST_F(TableViewTest, ResizeHonorsMinimum) {
  TableViewTestHelper helper(table_);
  const int x = table_->GetVisibleColumn(0).width;
  EXPECT_NE(0, x);

  PressLeftMouseAt(helper_->header(), gfx::Point(x, 0));
  DragLeftMouseTo(helper_->header(), gfx::Point(20, 0));

  int title_width = gfx::GetStringWidth(
      table_->GetVisibleColumn(0).column.title, helper.font_list());
  EXPECT_LT(title_width, table_->GetVisibleColumn(0).width);

  int old_width = table_->GetVisibleColumn(0).width;
  DragLeftMouseTo(helper_->header(), gfx::Point(old_width + 10, 0));
  EXPECT_EQ(old_width + 10, table_->GetVisibleColumn(0).width);
}

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

// Verifies clicking on the header sorts.
TEST_F(TableViewTest, SortOnMouse) {
  EXPECT_TRUE(table_->sort_descriptors().empty());

  const int x = table_->GetVisibleColumn(0).width / 2;
  EXPECT_NE(0, x);
  // Press and release the mouse.
  // The header must return true, else it won't normally get the release.
  EXPECT_TRUE(PressLeftMouseAt(helper_->header(), gfx::Point(x, 0)));
  ReleaseLeftMouseAt(helper_->header(), gfx::Point(x, 0));

  ASSERT_EQ(1u, table_->sort_descriptors().size());
  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
}

// Verifies that pressing the space bar when a particular visible column is
// active will sort by that column.
TEST_F(TableViewTest, SortOnSpaceBar) {
  if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell)
    return;

  table_->RequestFocus();
  ASSERT_TRUE(table_->sort_descriptors().empty());
  // Table starts off with no visible column being active.
  ASSERT_EQ(-1, helper_->GetActiveVisibleColumnIndex());

  ui::ListSelectionModel new_selection;
  new_selection.SetSelectedIndex(1);
  helper_->SetSelectionModel(new_selection);
  ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex());

  PressKey(ui::VKEY_SPACE);
  ASSERT_EQ(1u, table_->sort_descriptors().size());
  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);

  PressKey(ui::VKEY_SPACE);
  ASSERT_EQ(1u, table_->sort_descriptors().size());
  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);

  PressKey(ui::VKEY_RIGHT);
  ASSERT_EQ(1, helper_->GetActiveVisibleColumnIndex());

  PressKey(ui::VKEY_SPACE);
  ASSERT_EQ(2u, table_->sort_descriptors().size());
  EXPECT_EQ(1, table_->sort_descriptors()[0].column_id);
  EXPECT_EQ(0, table_->sort_descriptors()[1].column_id);
  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
  EXPECT_FALSE(table_->sort_descriptors()[1].ascending);
}

TEST_F(TableViewTest, Tooltip) {
  // Column 0 uses the TableModel's GetTooltipText override for tooltips.
  table_->SetVisibleColumnWidth(0, 10);
  auto local_point_for_row = [&](int row) {
    return gfx::Point(5, (row + 0.5) * table_->GetRowHeight());
  };
  auto expected = [](int row) {
    return base::ASCIIToUTF16("Tooltip") + base::NumberToString16(row);
  };
  EXPECT_EQ(expected(0), table_->GetTooltipText(local_point_for_row(0)));
  EXPECT_EQ(expected(1), table_->GetTooltipText(local_point_for_row(1)));
  EXPECT_EQ(expected(2), table_->GetTooltipText(local_point_for_row(2)));

  // Hovering another column will return that cell's text instead.
  const gfx::Point point(15, local_point_for_row(0).y());
  EXPECT_EQ(model_->GetText(0, 1), table_->GetTooltipText(point));
}

namespace {

class TableGrouperImpl : public TableGrouper {
 public:
  TableGrouperImpl() = default;

  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() = default;

  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_ = 0;

  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(nullptr);
}

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_->SetSelectOnRemove(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(nullptr);
}
#endif

// Verifies up/down correctly navigate 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(nullptr);
}

// Verifies left/right correctly navigate through visible columns.
TEST_F(TableViewTest, KeyLeftRight) {
  if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell)
    return;

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

  // Initially no active visible column.
  EXPECT_EQ(-1, helper_->GetActiveVisibleColumnIndex());

  PressKey(ui::VKEY_RIGHT);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  helper_->SetSelectionModel(ui::ListSelectionModel());
  EXPECT_EQ(-1, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(1, observer.GetChangedCountAndClear());

  PressKey(ui::VKEY_LEFT);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  PressKey(ui::VKEY_RIGHT);
  EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  PressKey(ui::VKEY_RIGHT);
  EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());

  ui::ListSelectionModel new_selection;
  new_selection.SetSelectedIndex(1);
  helper_->SetSelectionModel(new_selection);
  EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(1, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  PressKey(ui::VKEY_LEFT);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  PressKey(ui::VKEY_LEFT);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  table_->SetColumnVisibility(0, false);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  // Since the first column was hidden, the active visible column should not
  // advance.
  PressKey(ui::VKEY_RIGHT);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  // If visibility to the first column is restored, the active visible column
  // should be unchanged because columns are always added to the end.
  table_->SetColumnVisibility(0, true);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
  PressKey(ui::VKEY_RIGHT);
  EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());

  // If visibility to the first column is removed, the active visible column
  // should be decreased by one.
  table_->SetColumnVisibility(0, false);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  PressKey(ui::VKEY_LEFT);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  table_->SetColumnVisibility(0, true);
  EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  PressKey(ui::VKEY_RIGHT);
  EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
  EXPECT_EQ(0, observer.GetChangedCountAndClear());
  EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());

  table_->set_observer(nullptr);
}

// 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{2, 1, 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(nullptr);
}

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

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

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(nullptr);
}

// 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 = default;

  // WidgetDelegate:
  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


text:
require 'spec_helper'
require 'tempfile'

feature 'Builds', :feature do
  let(:user) { create(:user) }
  let(:user_access_level) { :developer }
  let(:project) { create(:project) }
  let(:pipeline) { create(:ci_pipeline, project: project) }

  let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
  let(:build2) { create(:ci_build) }

  let(:artifacts_file) do
    fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
  end

  before do
    project.team << [user, user_access_level]
    login_as(user)
  end

  describe "GET /:project/builds" do
    let!(:build) { create(:ci_build,  pipeline: pipeline) }

    context "Pending scope" do
      before do
        visit namespace_project_builds_path(project.namespace, project, scope: :pending)
      end

      it "shows Pending tab jobs" do
        expect(page).to have_link 'Cancel running'
        expect(page).to have_selector('.nav-links li.active', text: 'Pending')
        expect(page).to have_content build.short_sha
        expect(page).to have_content build.ref
        expect(page).to have_content build.name
      end
    end

    context "Running scope" do
      before do
        build.run!
        visit namespace_project_builds_path(project.namespace, project, scope: :running)
      end

      it "shows Running tab jobs" do
        expect(page).to have_selector('.nav-links li.active', text: 'Running')
        expect(page).to have_link 'Cancel running'
        expect(page).to have_content build.short_sha
        expect(page).to have_content build.ref
        expect(page).to have_content build.name
      end
    end

    context "Finished scope" do
      before do
        build.run!
        visit namespace_project_builds_path(project.namespace, project, scope: :finished)
      end

      it "shows Finished tab jobs" do
        expect(page).to have_selector('.nav-links li.active', text: 'Finished')
        expect(page).to have_content 'No jobs to show'
        expect(page).to have_link 'Cancel running'
      end
    end

    context "All jobs" do
      before do
        project.builds.running_or_pending.each(&:success)
        visit namespace_project_builds_path(project.namespace, project)
      end

      it "shows All tab jobs" do
        expect(page).to have_selector('.nav-links li.active', text: 'All')
        expect(page).to have_content build.short_sha
        expect(page).to have_content build.ref
        expect(page).to have_content build.name
        expect(page).not_to have_link 'Cancel running'
      end
    end
  end

  describe "POST /:project/builds/:id/cancel_all" do
    before do
      build.run!
      visit namespace_project_builds_path(project.namespace, project)
      click_link "Cancel running"
    end

    it 'shows all necessary content' do
      expect(page).to have_selector('.nav-links li.active', text: 'All')
      expect(page).to have_content 'canceled'
      expect(page).to have_content build.short_sha
      expect(page).to have_content build.ref
      expect(page).to have_content build.name
      expect(page).not_to have_link 'Cancel running'
    end
  end

  describe "GET /:project/builds/:id" do
    context "Job from project" do
      before do
        visit namespace_project_build_path(project.namespace, project, build)
      end

      it 'shows commit`s data' do
        expect(page.status_code).to eq(200)
        expect(page).to have_content pipeline.sha[0..7]
        expect(page).to have_content pipeline.git_commit_message
        expect(page).to have_content pipeline.git_author_name
      end

      it 'shows active build' do
        expect(page).to have_selector('.build-job.active')
      end
    end

    context "Job from other project" do
      before do
        visit namespace_project_build_path(project.namespace, project, build2)
      end

      it { expect(page.status_code).to eq(404) }
    end

    context "Download artifacts" do
      before do
        build.update_attributes(artifacts_file: artifacts_file)
        visit namespace_project_build_path(project.namespace, project, build)
      end

      it 'has button to download artifacts' do
        expect(page).to have_content 'Download'
      end
    end

    context 'Artifacts expire date' do
      before do
        build.update_attributes(artifacts_file: artifacts_file,
                                artifacts_expire_at: expire_at)

        visit namespace_project_build_path(project.namespace, project, build)
      end

      context 'no expire date defined' do
        let(:expire_at) { nil }

        it 'does not have the Keep button' do
          expect(page).not_to have_content 'Keep'
        end
      end

      context 'when expire date is defined' do
        let(:expire_at) { Time.now + 7.days }

        context 'when user has ability to update job' do
          it 'keeps artifacts when keep button is clicked' do
            expect(page).to have_content 'The artifacts will be removed'

            click_link 'Keep'

            expect(page).to have_no_link 'Keep'
            expect(page).to have_no_content 'The artifacts will be removed'
          end
        end

        context 'when user does not have ability to update job' do
          let(:user_access_level) { :guest }

          it 'does not have keep button' do
            expect(page).to have_no_link 'Keep'
          end
        end
      end

      context 'when artifacts expired' do
        let(:expire_at) { Time.now - 7.days }

        it 'does not have the Keep button' do
          expect(page).to have_content 'The artifacts were removed'
          expect(page).not_to have_link 'Keep'
        end
      end
    end

    feature 'Raw trace' do
      before do
        build.run!
        visit namespace_project_build_path(project.namespace, project, build)
      end

      it do
        expect(page).to have_link 'Raw'
      end
    end

    feature 'HTML trace', :js do
      before do
        build.run!

        visit namespace_project_build_path(project.namespace, project, build)
      end

      context 'when job has an initial trace' do
        it 'loads job trace' do
          expect(page).to have_content 'BUILD TRACE'

          build.trace.write do |stream|
            stream.append(' and more trace', 11)
          end

          expect(page).to have_content 'BUILD TRACE and more trace'
        end
      end
    end

    feature 'Variables' do
      let(:trigger_request) { create(:ci_trigger_request_with_variables) }

      let(:build) do
        create :ci_build, pipeline: pipeline, trigger_request: trigger_request
      end

      before do
        visit namespace_project_build_path(project.namespace, project, build)
      end

      it 'shows variable key and value after click', js: true do
        expect(page).to have_css('.reveal-variables')
        expect(page).not_to have_css('.js-build-variable')
        expect(page).not_to have_css('.js-build-value')

        click_button 'Reveal Variables'

        expect(page).not_to have_css('.reveal-variables')
        expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
        expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
      end
    end

    context 'when job starts environment' do
      let(:environment) { create(:environment, project: project) }
      let(:pipeline) { create(:ci_pipeline, project: project) }

      context 'job is successfull and has deployment' do
        let(:deployment) { create(:deployment) }
        let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }

        it 'shows a link for the job' do
          visit namespace_project_build_path(project.namespace, project, build)

          expect(page).to have_link environment.name
        end
      end

      context 'job is complete and not successfull' do
        let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }

        it 'shows a link for the job' do
          visit namespace_project_build_path(project.namespace, project, build)

          expect(page).to have_link environment.name
        end
      end

      context 'job creates a new deployment' do
        let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
        let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }

        it 'shows a link to latest deployment' do
          visit namespace_project_build_path(project.namespace, project, build)

          expect(page).to have_link('latest deployment')
        end
      end
    end
  end

  describe "POST /:project/builds/:id/cancel" do
    context "Job from project" do
      before do
        build.run!
        visit namespace_project_build_path(project.namespace, project, build)
        click_link "Cancel"
      end

      it 'loads the page and shows all needed controls' do
        expect(page.status_code).to eq(200)
        expect(page).to have_content 'canceled'
        expect(page).to have_content 'Retry'
      end
    end

    context "Job from other project" do
      before do
        build.run!
        visit namespace_project_build_path(project.namespace, project, build)
        page.driver.post(cancel_namespace_project_build_path(project.namespace, project, build2))
      end

      it { expect(page.status_code).to eq(404) }
    end
  end

  describe "POST /:project/builds/:id/retry" do
    context "Job from project" do
      before do
        build.run!
        visit namespace_project_build_path(project.namespace, project, build)
        click_link 'Cancel'
        page.within('.build-header') do
          click_link 'Retry job'
        end
      end

      it 'shows the right status and buttons' do
        expect(page).to have_http_status(200)
        expect(page).to have_content 'pending'
        page.within('aside.right-sidebar') do
          expect(page).to have_content 'Cancel'
        end
      end
    end

    context "Build from other project" do
      before do
        build.run!
        visit namespace_project_build_path(project.namespace, project, build)
        click_link 'Cancel'
        page.driver.post(retry_namespace_project_build_path(project.namespace, project, build2))
      end

      it { expect(page).to have_http_status(404) }
    end

    context "Build that current user is not allowed to retry" do
      before do
        build.run!
        build.cancel!
        project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)

        logout_direct
        login_with(create(:user))
        visit namespace_project_build_path(project.namespace, project, build)
      end

      it 'does not show the Retry button' do
        page.within('aside.right-sidebar') do
          expect(page).not_to have_content 'Retry'
        end
      end
    end
  end

  describe "GET /:project/builds/:id/download" do
    before do
      build.update_attributes(artifacts_file: artifacts_file)
      visit namespace_project_build_path(project.namespace, project, build)
      click_link 'Download'
    end

    context "Build from other project" do
      before do
        build2.update_attributes(artifacts_file: artifacts_file)
        visit download_namespace_project_build_artifacts_path(project.namespace, project, build2)
      end

      it { expect(page.status_code).to eq(404) }
    end
  end

  describe 'GET /:project/builds/:id/raw' do
    context 'access source' do
      context 'build from project' do
        before do
          Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
          build.run!
          visit namespace_project_build_path(project.namespace, project, build)
          page.within('.js-build-sidebar') { click_link 'Raw' }
        end

        it 'sends the right headers' do
          expect(page.status_code).to eq(200)
          expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
          expect(page.response_headers['X-Sendfile']).to eq(build.trace.send(:current_path))
        end
      end

      context 'build from other project' do
        before do
          Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
          build2.run!
          visit raw_namespace_project_build_path(project.namespace, project, build2)
        end

        it 'sends the right headers' do
          expect(page.status_code).to eq(404)
        end
      end
    end

    context 'storage form' do
      let(:existing_file) { Tempfile.new('existing-trace-file').path }

      before do
        Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')

        build.run!

        allow_any_instance_of(Gitlab::Ci::Trace).to receive(:paths)
          .and_return(paths)

        visit namespace_project_build_path(project.namespace, project, build)
      end

      context 'when build has trace in file' do
        let(:paths) do
          [existing_file]
        end

        before do
          page.within('.js-build-sidebar') { click_link 'Raw' }
        end

        it 'sends the right headers' do
          expect(page.status_code).to eq(200)
          expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
          expect(page.response_headers['X-Sendfile']).to eq(existing_file)
        end
      end

      context 'when build has trace in DB' do
        let(:paths) { [] }

        it 'sends the right headers' do
          expect(page.status_code).not_to have_link('Raw')
        end
      end
    end
  end

  describe "GET /:project/builds/:id/trace.json" do
    context "Build from project" do
      before do
        visit trace_namespace_project_build_path(project.namespace, project, build, format: :json)
      end

      it { expect(page.status_code).to eq(200) }
    end

    context "Build from other project" do
      before do
        visit trace_namespace_project_build_path(project.namespace, project, build2, format: :json)
      end

      it { expect(page.status_code).to eq(404) }
    end
  end

  describe "GET /:project/builds/:id/status" do
    context "Build from project" do
      before do
        visit status_namespace_project_build_path(project.namespace, project, build)
      end

      it { expect(page.status_code).to eq(200) }
    end

    context "Build from other project" do
      before do
        visit status_namespace_project_build_path(project.namespace, project, build2)
      end

      it { expect(page.status_code).to eq(404) }
    end
  end
end

redpajama_set_name: RedPajamaGithub


text:
<?php

namespace Nette\Caching;

use Nette,
	Nette\Utils\Callback;

/**
 * Implements the cache for a application.
 *
 * @author     David Grudl
 *
 * @property-read IStorage $storage
 * @property-read string $namespace
 */
class Cache extends Nette\Object implements \ArrayAccess
{
	/** dependency */
	const PRIORITY = 'priority',
		EXPIRATION = 'expire',
		EXPIRE = 'expire',
		SLIDING = 'sliding',
		TAGS = 'tags',
		FILES = 'files',
		ITEMS = 'items',
		CONSTS = 'consts',
		CALLBACKS = 'callbacks',
		ALL = 'all';

	/** @internal */
	const NAMESPACE_SEPARATOR = "\x00";

	/** @var IStorage */
	private $storage;

	/** @var string */
	private $namespace;

	/** @var string  last query cache used by offsetGet() */
	private $key;

	/** @var mixed  last query cache used by offsetGet()  */
	private $data;

	public function __construct(IStorage $storage, $namespace = NULL)
	{
		$this->storage = $storage;
		$this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
	}

	/**
	 * Returns cache storage.
	 * @return IStorage
	 */
	public function getStorage()
	{
		return $this->storage;
	}

	/**
	 * Returns cache namespace.
	 * @return string
	 */
	public function getNamespace()
	{
		return (string) substr($this->namespace, 0, -1);
	}

	/**
	 * Returns new nested cache object.
	 * @param  string
	 * @return Cache
	 */
	public function derive($namespace)
	{
		$derived = new static($this->storage, $this->namespace . $namespace);
		return $derived;
	}

	/**
	 * Reads the specified item from the cache or generate it.
	 * @param  mixed key
	 * @param  callable
	 * @return mixed|NULL
	 */
	public function load($key, $fallback = NULL)
	{
		$data = $this->storage->read($this->generateKey($key));
		if ($data === NULL && $fallback) {
			return $this->save($key, Callback::closure($fallback));
		}
		return $data;
	}

	/**
	 * Writes item into the cache.
	 * Dependencies are:
	 * - Cache::PRIORITY => (int) priority
	 * - Cache::EXPIRATION => (timestamp) expiration
	 * - Cache::SLIDING => (bool) use sliding expiration?
	 * - Cache::TAGS => (array) tags
	 * - Cache::FILES => (array|string) file names
	 * - Cache::ITEMS => (array|string) cache items
	 * - Cache::CONSTS => (array|string) cache items
	 *
	 * @param  mixed  key
	 * @param  mixed  value
	 * @param  array  dependencies
	 * @return mixed  value itself
	 * @throws Nette\InvalidArgumentException
	 */
	public function save($key, $data, array $dependencies = NULL)
	{
		$this->release();
		$key = $this->generateKey($key);

		if ($data instanceof Nette\Callback || $data instanceof \Closure) {
			$this->storage->lock($key);
			$data = call_user_func_array($data, array(& $dependencies));
		}

		if ($data === NULL) {
			$this->storage->remove($key);
		} else {
			$this->storage->write($key, $data, $this->completeDependencies($dependencies, $data));
			return $data;
		}
	}

	private function completeDependencies($dp, $data)
	{
		if (is_object($data)) {
			$dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkSerializationVersion'), get_class($data),
				Nette\Reflection\ClassType::from($data)->getAnnotation('serializationVersion'));
		}

		// convert expire into relative amount of seconds
		if (isset($dp[Cache::EXPIRATION])) {
			$dp[Cache::EXPIRATION] = Nette\DateTime::from($dp[Cache::EXPIRATION])->format('U') - time();
		}

		// convert FILES into CALLBACKS
		if (isset($dp[self::FILES])) {
			//clearstatcache();
			foreach (array_unique((array) $dp[self::FILES]) as $item) {
				$dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkFile'), $item, @filemtime($item)); // @ - stat may fail
			}
			unset($dp[self::FILES]);
		}

		// add namespaces to items
		if (isset($dp[self::ITEMS])) {
			$dp[self::ITEMS] = array_unique(array_map(array($this, 'generateKey'), (array) $dp[self::ITEMS]));
		}

		// convert CONSTS into CALLBACKS
		if (isset($dp[self::CONSTS])) {
			foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
				$dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkConst'), $item, constant($item));
			}
			unset($dp[self::CONSTS]);
		}

		if (!is_array($dp)) {
			$dp = array();
		}
		return $dp;
	}

	/**
	 * Removes item from the cache.
	 * @param  mixed  key
	 * @return void
	 */
	public function remove($key)
	{
		$this->save($key, NULL);
	}

	/**
	 * Removes items from the cache by conditions.
	 * Conditions are:
	 * - Cache::PRIORITY => (int) priority
	 * - Cache::TAGS => (array) tags
	 * - Cache::ALL => TRUE
	 * @return void
	 */
	public function clean(array $conditions = NULL)
	{
		$this->release();
		$this->storage->clean((array) $conditions);
	}

	/**
	 * Caches results of function/method calls.
	 * @param  mixed
	 * @return mixed
	 */
	public function call($function)
	{
		$key = func_get_args();
		$key[0] = Callback::toReflection($function);
		return $this->load($key, function() use ($function, $key) {
			return Callback::invokeArgs($function, array_slice($key, 1));
		});
	}

	/**
	 * Caches results of function/method calls.
	 * @param  mixed
	 * @param  array  dependencies
	 * @return Closure
	 */
	public function wrap($function, array $dependencies = NULL)
	{
		$cache = $this;
		return function() use ($cache, $function, $dependencies) {
			$key = array(Callback::toReflection($function), func_get_args());
			$data = $cache->load($key);
			if ($data === NULL) {
				$data = $cache->save($key, Callback::invokeArgs($function, $key[1]), $dependencies);
			}
			return $data;
		};
	}

	/**
	 * Starts the output cache.
	 * @param  mixed  key
	 * @return OutputHelper|NULL
	 */
	public function start($key)
	{
		$data = $this->load($key);
		if ($data === NULL) {
			return new OutputHelper($this, $key);
		}
		echo $data;
	}

	/**
	 * Generates internal cache key.
	 *
	 * @param  string
	 * @return string
	 */
	protected function generateKey($key)
	{
		return $this->namespace . md5(is_scalar($key) ? $key : serialize($key));
	}

	/********************* interface ArrayAccess ****************d*g**/

	/**
	 * Inserts (replaces) item into the cache (\ArrayAccess implementation).
	 * @param  mixed key
	 * @param  mixed
	 * @return void
	 * @throws Nette\InvalidArgumentException
	 */
	public function offsetSet($key, $data)
	{
		$this->save($key, $data);
	}

	/**
	 * Retrieves the specified item from the cache or NULL if the key is not found (\ArrayAccess implementation).
	 * @param  mixed key
	 * @return mixed|NULL
	 * @throws Nette\InvalidArgumentException
	 */
	public function offsetGet($key)
	{
		$key = is_scalar($key) ? (string) $key : serialize($key);
		if ($this->key !== $key) {
			$this->key = $key;
			$this->data = $this->load($key);
		}
		return $this->data;
	}

	/**
	 * Exists item in cache? (\ArrayAccess implementation).
	 * @param  mixed key
	 * @return bool
	 * @throws Nette\InvalidArgumentException
	 */
	public function offsetExists($key)
	{
		$this->release();
		return $this->offsetGet($key) !== NULL;
	}

	/**
	 * Removes the specified item from the cache.
	 * @param  mixed key
	 * @return void
	 * @throws Nette\InvalidArgumentException
	 */
	public function offsetUnset($key)
	{
		$this->save($key, NULL);
	}

	/**
	 * Discards the internal cache used by ArrayAccess.
	 * @return void
	 */
	public function release()
	{
		$this->key = $this->data = NULL;
	}

	/********************* dependency checkers ****************d*g**/

	/**
	 * Checks CALLBACKS dependencies.
	 * @param  array
	 * @return bool
	 */
	public static function checkCallbacks($callbacks)
	{
		foreach ($callbacks as $callback) {
			if (!call_user_func_array(array_shift($callback), $callback)) {
				return FALSE;
			}
		}
		return TRUE;
	}

	/**
	 * Checks CONSTS dependency.
	 * @param  string
	 * @param  mixed
	 * @return bool
	 */
	private static function checkConst($const, $value)
	{
		return defined($const) && constant($const) === $value;
	}

	/**
	 * Checks FILES dependency.
	 * @param  string
	 * @param  int
	 * @return bool
	 */
	private static function checkFile($file, $time)
	{
		return @filemtime($file) == $time; // @ - stat may fail
	}

	/**
	 * Checks object @serializationVersion label.
	 * @param  string
	 * @param  mixed
	 * @return bool
	 */
	private static function checkSerializationVersion($class, $value)
	{
		return Nette\Reflection\ClassType::from($class)->getAnnotation('serializationVersion') === $value;
	}

}


text:
package org.apache.eagle.common;

import java.util.AbstractList;
import java.util.RandomAccess;

/**
 * Circular array implementation
 *
 * @param <E>
 */
public class CircularArrayList<E> extends AbstractList<E> implements RandomAccess {

    private final E[] buf; // a List implementing RandomAccess
    private int head = 0;
    private int tail = 0;
    private boolean full = false;

    public CircularArrayList(E[] array) {
        buf = array;
        full = (buf.length == 0);
    }

    public int capacity() {
        return buf.length;
    }

    public int head() {
    	return head;
    }

    public int tail() {
    	return tail;
    }

    public boolean isFull() {
    	return full;
    }

    @Override
    public void clear() {
        head = 0;
        tail = 0;
        full = false;
        for (int i = 0; i < buf.length; ++i) {
        	buf[i] = null;
        }
    }

    private int wrapIndex(int i) {
        int m = i % buf.length;
        if (m < 0) { // java modulus can be negative
            throw new IndexOutOfBoundsException();
        }
        return m;
    }

    // This method is O(n) but will never be called if the
    // CircularArrayList is used in its typical/intended role.
    private void shiftBlock(int startIndex, int endIndex) {
        assert (endIndex > startIndex);
        for (int i = endIndex - 1; i >= startIndex; i--) {
            set(i + 1, get(i));
        }
    }

    public int find(E e) {
    	final int size = size();
    	for (int i = 0; i < size; ++i) {
    		if (e.equals(get(i))) {
    			return i;
    		}
    	}
    	return -1;
    }

    @Override
    public int size() {
    	if (full) {
    		return buf.length;
    	}
        return tail - head + (tail < head ? buf.length : 0);
    }

    @Override
    public E get(int i) {
        if (i < 0 || i >= size()) {
            throw new IndexOutOfBoundsException();
        }
        return buf[wrapIndex(head + i)];
    }

    @Override
    public E set(int i, E e) {
        if (i < 0 || i >= size()) {
            throw new IndexOutOfBoundsException();
        }
        return buf[wrapIndex(head + i)] =  e;
    }

    @Override
    public void add(int i, E e) {
        int s = size();
        if (s == buf.length) {
            throw new IllegalStateException("Cannot add element."
                    + " CircularArrayList is filled to capacity.");
        }
        full = (s + 1 == buf.length);
        if (i < 0 || i > s) {
            throw new IndexOutOfBoundsException();
        }
        tail = wrapIndex(tail + 1);
        if (i < s) {
            shiftBlock(i, s);
        }
        set(i, e);
    }

    @Override
    public E remove(int i) {
        int s = size();
        if (i < 0 || i >= s) {
            throw new IndexOutOfBoundsException();
        }
        final E e = get(i);
        if (i > 0) {
            shiftBlock(0, i);
        }
    	buf[head] = null;
        head = wrapIndex(head + 1);
        full = false;
        return e;
    }
}


text:
//-CRE-
using System;
using System.Collections.Specialized;
using System.Dynamic;
using System.IO;
using System.Linq.Expressions;
using Glass.Mapper.Sc.RenderField;
using System.Web.UI;
using Sitecore.Web.UI.WebControls;

namespace Glass.Mapper.Sc.Web.Ui
{
    /// <summary>
    /// Class GlassUserControl
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GlassUserControl<T> : AbstractGlassUserControl where T : class
    {

        /// <summary>
        /// Model to render on the sublayout
        /// </summary>
        /// <value>The model.</value>
        public T Model { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether [infer type].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [infer type]; otherwise, <c>false</c>.
        /// </value>
        public bool InferType { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is lazy.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is lazy; otherwise, <c>false</c>.
        /// </value>
        public bool IsLazy { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="GlassUserControl{T}"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        public GlassUserControl(ISitecoreContext context) : base(context) { }
        /// <summary>
        /// Initializes a new instance of the <see cref="GlassUserControl{T}"/> class.
        /// </summary>
        public GlassUserControl() : base() { }

        /// <summary>
        /// Gets the model.
        /// </summary>
        protected virtual void GetModel()
        {

            Model = SitecoreContext.CreateType<T>(LayoutItem, IsLazy, InferType);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Load" /> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs" /> object that contains the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            GetModel();
            base.OnLoad(e);
        }

        /// <summary>
        /// Makes a field editable via the Page Editor. Use the Model property as the target item, e.g. model => model.Title where Title is field name.
        /// </summary>
        /// <param name="field">The field.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.String.</returns>
        public string Editable(Expression<Func<T, object>> field, object parameters = null)
        {
            return base.Editable(this.Model, field, parameters);
        }

        /// <summary>
        /// Makes a field editable via the Page Editor. Use the Model property as the target item, e.g. model => model.Title where Title is field name.
        /// </summary>
        /// <param name="field">The field.</param>
        /// <param name="standardOutput">The standard output.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.String.</returns>
        public string Editable(Expression<Func<T, object>> field, Expression<Func<T, string>> standardOutput,
                               object parameters = null)
        {
            return base.Editable(this.Model, field, standardOutput, parameters);
        }

        /// <summary>
        /// Renders an image allowing simple page editor support
        /// </summary>
        /// <typeparam name="T">The model type</typeparam>
        /// <param name="model">The model that contains the image field</param>
        /// <param name="field">A lambda expression to the image field, should be of type Glass.Mapper.Sc.Fields.Image</param>
        /// <param name="parameters">Image parameters, e.g. width, height</param>
        /// <param name="isEditable">Indicates if the field should be editable</param>
        /// <returns></returns>
        public virtual string RenderImage(Expression<Func<T, object>> field,
                                             object parameters = null,
                                             bool isEditable = false)
        {
            return base.RenderImage(this.Model, field, parameters, isEditable);
        }

        /// <summary>
        /// Render HTML for a link with contents
        /// </summary>
        /// <typeparam name="T">The model type</typeparam>
        /// <param name="model">The model</param>
        /// <param name="field">The link field to user</param>
        /// <param name="attributes">Any additional link attributes</param>
        /// <param name="isEditable">Make the link editable</param>
        /// <returns></returns>
        public virtual RenderingResult BeginRenderLink(Expression<Func<T, object>> field,
                                                     object attributes = null, bool isEditable = false)
        {
            return GlassHtml.BeginRenderLink(this.Model, field, this.Output, attributes, isEditable);

        }

        /// <summary>
        /// Render HTML for a link
        /// </summary>
        /// <typeparam name="T">The model type</typeparam>
        /// <param name="model">The model</param>
        /// <param name="field">The link field to user</param>
        /// <param name="attributes">Any additional link attributes</param>
        /// <param name="isEditable">Make the link editable</param>
        /// <param name="contents">Content to override the default decription or item name</param>
        /// <returns></returns>
        public virtual string RenderLink(Expression<Func<T, object>> field, object attributes = null, bool isEditable = false, string contents = null)
        {

            return GlassHtml.RenderLink(this.Model, field, attributes, isEditable, contents);
        }

        private string GetRenderingParameters(Control control)
        {
            if (control == null) return null;

            if (control is Sublayout)
            {
                return ((Sublayout) control).Parameters;
            }

            return GetRenderingParameters(control.Parent);
        }

        public virtual string RenderingParameters{get { return GetRenderingParameters(this); }}

        public virtual T GetRenderingParameters<T>() where T: class
        {
            return GlassHtml.GetRenderingParameters<T>(RenderingParameters);
        }

    }
}


text:
package org.incode.eurocommercial.ecpcrm.module.application.fixture.security.roles;

import org.isisaddons.module.security.dom.permission.ApplicationPermissionMode;
import org.isisaddons.module.security.dom.permission.ApplicationPermissionRule;
import org.isisaddons.module.security.seed.scripts.AbstractRoleAndPermissionsFixtureScript;

public class AuditModuleRoleAndPermissions extends AbstractRoleAndPermissionsFixtureScript {

    public static final String ROLE_NAME = "ecpcrm-auditing-admin";

    public AuditModuleRoleAndPermissions() {
        super(ROLE_NAME, "Admin access to audit module");
    }

    @Override
    protected void execute(final ExecutionContext executionContext) {
        newPackagePermissions(
                ApplicationPermissionRule.ALLOW,
                ApplicationPermissionMode.CHANGING,
                "org.isisaddons.module.audit");
    }

}