1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/views/controls/table/table_view.h"
6
7#include "base/strings/string_number_conversions.h"
8#include "base/strings/utf_string_conversions.h"
9#include "testing/gtest/include/gtest/gtest.h"
10#include "ui/views/controls/table/table_grouper.h"
11#include "ui/views/controls/table/table_header.h"
12#include "ui/views/controls/table/table_view_observer.h"
13
14// Put the tests in the views namespace to make it easier to declare them as
15// friend classes.
16namespace views {
17
18class TableViewTestHelper {
19 public:
20  explicit TableViewTestHelper(TableView* table) : table_(table) {}
21
22  std::string GetPaintRegion(const gfx::Rect& bounds) {
23    TableView::PaintRegion region(table_->GetPaintRegion(bounds));
24    return "rows=" + base::IntToString(region.min_row) + " " +
25        base::IntToString(region.max_row) + " cols=" +
26        base::IntToString(region.min_column) + " " +
27        base::IntToString(region.max_column);
28  }
29
30  size_t visible_col_count() {
31    return table_->visible_columns().size();
32  }
33
34  TableHeader* header() { return table_->header_; }
35
36 private:
37  TableView* table_;
38
39  DISALLOW_COPY_AND_ASSIGN(TableViewTestHelper);
40};
41
42namespace {
43
44// TestTableModel2 -------------------------------------------------------------
45
46// Trivial TableModel implementation that is backed by a vector of vectors.
47// Provides methods for adding/removing/changing the contents that notify the
48// observer appropriately.
49//
50// Initial contents are:
51// 0, 1
52// 1, 1
53// 2, 2
54// 3, 0
55class TestTableModel2 : public ui::TableModel {
56 public:
57  TestTableModel2();
58
59  // Adds a new row at index |row| with values |c1_value| and |c2_value|.
60  void AddRow(int row, int c1_value, int c2_value);
61
62  // Removes the row at index |row|.
63  void RemoveRow(int row);
64
65  // Changes the values of the row at |row|.
66  void ChangeRow(int row, int c1_value, int c2_value);
67
68  // ui::TableModel:
69  virtual int RowCount() OVERRIDE;
70  virtual string16 GetText(int row, int column_id) OVERRIDE;
71  virtual void SetObserver(ui::TableModelObserver* observer) OVERRIDE;
72  virtual int CompareValues(int row1, int row2, int column_id) OVERRIDE;
73
74 private:
75  ui::TableModelObserver* observer_;
76
77  // The data.
78  std::vector<std::vector<int> > rows_;
79
80  DISALLOW_COPY_AND_ASSIGN(TestTableModel2);
81};
82
83TestTableModel2::TestTableModel2() : observer_(NULL) {
84  AddRow(0, 0, 1);
85  AddRow(1, 1, 1);
86  AddRow(2, 2, 2);
87  AddRow(3, 3, 0);
88}
89
90void TestTableModel2::AddRow(int row, int c1_value, int c2_value) {
91  DCHECK(row >= 0 && row <= static_cast<int>(rows_.size()));
92  std::vector<int> new_row;
93  new_row.push_back(c1_value);
94  new_row.push_back(c2_value);
95  rows_.insert(rows_.begin() + row, new_row);
96  if (observer_)
97    observer_->OnItemsAdded(row, 1);
98}
99void TestTableModel2::RemoveRow(int row) {
100  DCHECK(row >= 0 && row <= static_cast<int>(rows_.size()));
101  rows_.erase(rows_.begin() + row);
102  if (observer_)
103    observer_->OnItemsRemoved(row, 1);
104}
105
106void TestTableModel2::ChangeRow(int row, int c1_value, int c2_value) {
107  DCHECK(row >= 0 && row < static_cast<int>(rows_.size()));
108  rows_[row][0] = c1_value;
109  rows_[row][1] = c2_value;
110  if (observer_)
111    observer_->OnItemsChanged(row, 1);
112}
113
114int TestTableModel2::RowCount() {
115  return static_cast<int>(rows_.size());
116}
117
118string16 TestTableModel2::GetText(int row, int column_id) {
119  return base::IntToString16(rows_[row][column_id]);
120}
121
122void TestTableModel2::SetObserver(ui::TableModelObserver* observer) {
123  observer_ = observer;
124}
125
126int TestTableModel2::CompareValues(int row1, int row2, int column_id) {
127  return rows_[row1][column_id] - rows_[row2][column_id];
128}
129
130// Returns the view to model mapping as a string.
131std::string GetViewToModelAsString(TableView* table) {
132  std::string result;
133  for (int i = 0; i < table->RowCount(); ++i) {
134    if (i != 0)
135      result += " ";
136    result += base::IntToString(table->ViewToModel(i));
137  }
138  return result;
139}
140
141// Returns the model to view mapping as a string.
142std::string GetModelToViewAsString(TableView* table) {
143  std::string result;
144  for (int i = 0; i < table->RowCount(); ++i) {
145    if (i != 0)
146      result += " ";
147    result += base::IntToString(table->ModelToView(i));
148  }
149  return result;
150}
151
152class TestTableView : public TableView {
153 public:
154  TestTableView(ui::TableModel* model,
155                const std::vector<ui::TableColumn>& columns)
156      : TableView(model, columns, TEXT_ONLY, false) {
157  }
158
159  // View overrides:
160  virtual bool HasFocus() const OVERRIDE {
161    // Overriden so key processing works.
162    return true;
163  }
164
165 private:
166  DISALLOW_COPY_AND_ASSIGN(TestTableView);
167};
168
169}  // namespace
170
171class TableViewTest : public testing::Test {
172 public:
173  TableViewTest() : table_(NULL) {}
174
175  virtual void SetUp() OVERRIDE {
176    model_.reset(new TestTableModel2);
177    std::vector<ui::TableColumn> columns(2);
178    columns[0].title = ASCIIToUTF16("Title Column 0");
179    columns[0].sortable = true;
180    columns[1].title = ASCIIToUTF16("Title Column 1");
181    columns[1].id = 1;
182    columns[1].sortable = true;
183    table_ = new TestTableView(model_.get(), columns);
184    parent_.reset(table_->CreateParentIfNecessary());
185    parent_->SetBounds(0, 0, 10000, 10000);
186    parent_->Layout();
187    helper_.reset(new TableViewTestHelper(table_));
188  }
189
190  void ClickOnRow(int row, int flags) {
191    const int y = row * table_->row_height();
192    const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(0, y),
193                                 gfx::Point(0, y),
194                                 ui::EF_LEFT_MOUSE_BUTTON | flags);
195    table_->OnMousePressed(pressed);
196  }
197
198  // Returns the state of the selection model as a string. The format is:
199  // 'active=X anchor=X selection=X X X...'.
200  std::string SelectionStateAsString() const {
201    const ui::ListSelectionModel& model(table_->selection_model());
202    std::string result = "active=" + base::IntToString(model.active()) +
203        " anchor=" + base::IntToString(model.anchor()) +
204        " selection=";
205    const ui::ListSelectionModel::SelectedIndices& selection(
206        model.selected_indices());
207    for (size_t i = 0; i < selection.size(); ++i) {
208      if (i != 0)
209        result += " ";
210      result += base::IntToString(selection[i]);
211    }
212    return result;
213  }
214
215  void PressKey(ui::KeyboardCode code) {
216    ui::KeyEvent event(ui::ET_KEY_PRESSED, code, 0, false);
217    table_->OnKeyPressed(event);
218  }
219
220 protected:
221  scoped_ptr<TestTableModel2> model_;
222
223  // Owned by |parent_|.
224  TableView* table_;
225
226  scoped_ptr<TableViewTestHelper> helper_;
227
228 private:
229  scoped_ptr<View> parent_;
230
231  DISALLOW_COPY_AND_ASSIGN(TableViewTest);
232};
233
234// Verifies GetPaintRegion.
235TEST_F(TableViewTest, GetPaintRegion) {
236  // Two columns should be visible.
237  EXPECT_EQ(2u, helper_->visible_col_count());
238
239  EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds()));
240  EXPECT_EQ("rows=0 4 cols=0 1",
241            helper_->GetPaintRegion(gfx::Rect(0, 0, 1, table_->height())));
242}
243
244// Verifies SetColumnVisibility().
245TEST_F(TableViewTest, ColumnVisibility) {
246  // Two columns should be visible.
247  EXPECT_EQ(2u, helper_->visible_col_count());
248
249  // Should do nothing (column already visible).
250  table_->SetColumnVisibility(0, true);
251  EXPECT_EQ(2u, helper_->visible_col_count());
252
253  // Hide the first column.
254  table_->SetColumnVisibility(0, false);
255  ASSERT_EQ(1u, helper_->visible_col_count());
256  EXPECT_EQ(1, table_->visible_columns()[0].column.id);
257  EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds()));
258
259  // Hide the second column.
260  table_->SetColumnVisibility(1, false);
261  EXPECT_EQ(0u, helper_->visible_col_count());
262
263  // Show the second column.
264  table_->SetColumnVisibility(1, true);
265  ASSERT_EQ(1u, helper_->visible_col_count());
266  EXPECT_EQ(1, table_->visible_columns()[0].column.id);
267  EXPECT_EQ("rows=0 4 cols=0 1", helper_->GetPaintRegion(table_->bounds()));
268
269  // Show the first column.
270  table_->SetColumnVisibility(0, true);
271  ASSERT_EQ(2u, helper_->visible_col_count());
272  EXPECT_EQ(1, table_->visible_columns()[0].column.id);
273  EXPECT_EQ(0, table_->visible_columns()[1].column.id);
274  EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds()));
275}
276
277// Verifies resizing a column works.
278TEST_F(TableViewTest, Resize) {
279  const int x = table_->visible_columns()[0].width;
280  EXPECT_NE(0, x);
281  // Drag the mouse 1 pixel to the left.
282  const ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(x, 0),
283                               gfx::Point(x, 0), ui::EF_LEFT_MOUSE_BUTTON);
284  helper_->header()->OnMousePressed(pressed);
285  const ui::MouseEvent dragged(ui::ET_MOUSE_DRAGGED, gfx::Point(x - 1, 0),
286                               gfx::Point(x - 1, 0), ui::EF_LEFT_MOUSE_BUTTON);
287  helper_->header()->OnMouseDragged(dragged);
288
289  // This should shrink the first column and pull the second column in.
290  EXPECT_EQ(x - 1, table_->visible_columns()[0].width);
291  EXPECT_EQ(x - 1, table_->visible_columns()[1].x);
292}
293
294// Assertions for table sorting.
295TEST_F(TableViewTest, Sort) {
296  // Toggle the sort order of the first column, shouldn't change anything.
297  table_->ToggleSortOrder(0);
298  ASSERT_EQ(1u, table_->sort_descriptors().size());
299  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
300  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
301  EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
302  EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
303
304  // Invert the sort (first column descending).
305  table_->ToggleSortOrder(0);
306  ASSERT_EQ(1u, table_->sort_descriptors().size());
307  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
308  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
309  EXPECT_EQ("3 2 1 0", GetViewToModelAsString(table_));
310  EXPECT_EQ("3 2 1 0", GetModelToViewAsString(table_));
311
312  // Change cell 0x3 to -1, meaning we have 0, 1, 2, -1 (in the first column).
313  model_->ChangeRow(3, -1, 0);
314  ASSERT_EQ(1u, table_->sort_descriptors().size());
315  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
316  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
317  EXPECT_EQ("2 1 0 3", GetViewToModelAsString(table_));
318  EXPECT_EQ("2 1 0 3", GetModelToViewAsString(table_));
319
320  // Invert sort again (first column ascending).
321  table_->ToggleSortOrder(0);
322  ASSERT_EQ(1u, table_->sort_descriptors().size());
323  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
324  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
325  EXPECT_EQ("3 0 1 2", GetViewToModelAsString(table_));
326  EXPECT_EQ("1 2 3 0", GetModelToViewAsString(table_));
327
328  // Add a row so that model has 0, 3, 1, 2, -1.
329  model_->AddRow(1, 3, 4);
330  ASSERT_EQ(1u, table_->sort_descriptors().size());
331  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
332  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
333  EXPECT_EQ("4 0 2 3 1", GetViewToModelAsString(table_));
334  EXPECT_EQ("1 4 2 3 0", GetModelToViewAsString(table_));
335
336  // Delete the first row, ending up with 3, 1, 2, -1.
337  model_->RemoveRow(0);
338  ASSERT_EQ(1u, table_->sort_descriptors().size());
339  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
340  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
341  EXPECT_EQ("3 1 2 0", GetViewToModelAsString(table_));
342  EXPECT_EQ("3 1 2 0", GetModelToViewAsString(table_));
343}
344
345namespace {
346
347class TableGrouperImpl : public TableGrouper {
348 public:
349  TableGrouperImpl() {}
350
351  void SetRanges(const std::vector<int>& ranges) {
352    ranges_ = ranges;
353  }
354
355  // TableGrouper overrides:
356  virtual void GetGroupRange(int model_index, GroupRange* range) OVERRIDE {
357    int offset = 0;
358    size_t range_index = 0;
359    for (; range_index < ranges_.size() && offset < model_index; ++range_index)
360      offset += ranges_[range_index];
361
362    if (offset == model_index) {
363      range->start = model_index;
364      range->length = ranges_[range_index];
365    } else {
366      range->start = offset - ranges_[range_index - 1];
367      range->length = ranges_[range_index - 1];
368    }
369  }
370
371 private:
372  std::vector<int> ranges_;
373
374  DISALLOW_COPY_AND_ASSIGN(TableGrouperImpl);
375};
376
377}  // namespace
378
379// Assertions around grouping.
380TEST_F(TableViewTest, Grouping) {
381  // Configure the grouper so that there are two groups:
382  // A 0
383  //   1
384  // B 2
385  //   3
386  TableGrouperImpl grouper;
387  std::vector<int> ranges;
388  ranges.push_back(2);
389  ranges.push_back(2);
390  grouper.SetRanges(ranges);
391  table_->SetGrouper(&grouper);
392
393  // Toggle the sort order of the first column, shouldn't change anything.
394  table_->ToggleSortOrder(0);
395  ASSERT_EQ(1u, table_->sort_descriptors().size());
396  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
397  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
398  EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
399  EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
400
401  // Sort descending, resulting:
402  // B 2
403  //   3
404  // A 0
405  //   1
406  table_->ToggleSortOrder(0);
407  ASSERT_EQ(1u, table_->sort_descriptors().size());
408  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
409  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
410  EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_));
411  EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_));
412
413  // Change the entry in the 4th row to -1. The model now becomes:
414  // A 0
415  //   1
416  // B 2
417  //   -1
418  // Since the first entry in the range didn't change the sort isn't impacted.
419  model_->ChangeRow(3, -1, 0);
420  ASSERT_EQ(1u, table_->sort_descriptors().size());
421  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
422  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
423  EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_));
424  EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_));
425
426  // Change the entry in the 3rd row to -1. The model now becomes:
427  // A 0
428  //   1
429  // B -1
430  //   -1
431  model_->ChangeRow(2, -1, 0);
432  ASSERT_EQ(1u, table_->sort_descriptors().size());
433  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
434  EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
435  EXPECT_EQ("0 1 2 3", GetViewToModelAsString(table_));
436  EXPECT_EQ("0 1 2 3", GetModelToViewAsString(table_));
437
438  // Toggle to ascending sort.
439  table_->ToggleSortOrder(0);
440  ASSERT_EQ(1u, table_->sort_descriptors().size());
441  EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
442  EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
443  EXPECT_EQ("2 3 0 1", GetViewToModelAsString(table_));
444  EXPECT_EQ("2 3 0 1", GetModelToViewAsString(table_));
445}
446
447namespace {
448
449class TableViewObserverImpl : public TableViewObserver {
450 public:
451  TableViewObserverImpl() : selection_changed_count_(0) {}
452
453  int GetChangedCountAndClear() {
454    const int count = selection_changed_count_;
455    selection_changed_count_ = 0;
456    return count;
457  }
458
459  // TableViewObserver overrides:
460  virtual void OnSelectionChanged() OVERRIDE {
461    selection_changed_count_++;
462  }
463
464 private:
465  int selection_changed_count_;
466
467  DISALLOW_COPY_AND_ASSIGN(TableViewObserverImpl);
468};
469
470}  // namespace
471
472// Assertions around changing the selection.
473TEST_F(TableViewTest, Selection) {
474  TableViewObserverImpl observer;
475  table_->SetObserver(&observer);
476
477  // Initially no selection.
478  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
479
480  // Select the last row.
481  table_->Select(3);
482  EXPECT_EQ(1, observer.GetChangedCountAndClear());
483  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
484
485  // Change sort, shouldn't notify of change (toggle twice so that order
486  // actually changes).
487  table_->ToggleSortOrder(0);
488  table_->ToggleSortOrder(0);
489  EXPECT_EQ(0, observer.GetChangedCountAndClear());
490  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
491
492  // Remove the selected row, this should notify of a change and update the
493  // selection.
494  model_->RemoveRow(3);
495  EXPECT_EQ(1, observer.GetChangedCountAndClear());
496  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
497
498  // Insert a row, since the selection in terms of the original model hasn't
499  // changed the observer is not notified.
500  model_->AddRow(0, 1, 2);
501  EXPECT_EQ(0, observer.GetChangedCountAndClear());
502  EXPECT_EQ("active=3 anchor=3 selection=3", SelectionStateAsString());
503
504  table_->SetObserver(NULL);
505}
506
507// Verifies up/down correctly navigates through groups.
508TEST_F(TableViewTest, KeyUpDown) {
509  // Configure the grouper so that there are three groups:
510  // A 0
511  //   1
512  // B 5
513  // C 2
514  //   3
515  model_->AddRow(2, 5, 0);
516  TableGrouperImpl grouper;
517  std::vector<int> ranges;
518  ranges.push_back(2);
519  ranges.push_back(1);
520  ranges.push_back(2);
521  grouper.SetRanges(ranges);
522  table_->SetGrouper(&grouper);
523
524  TableViewObserverImpl observer;
525  table_->SetObserver(&observer);
526
527  // Initially no selection.
528  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
529
530  PressKey(ui::VKEY_DOWN);
531  EXPECT_EQ(1, observer.GetChangedCountAndClear());
532  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
533
534  PressKey(ui::VKEY_DOWN);
535  EXPECT_EQ(1, observer.GetChangedCountAndClear());
536  EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
537
538  PressKey(ui::VKEY_DOWN);
539  EXPECT_EQ(1, observer.GetChangedCountAndClear());
540  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
541
542  PressKey(ui::VKEY_DOWN);
543  EXPECT_EQ(1, observer.GetChangedCountAndClear());
544  EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
545
546  PressKey(ui::VKEY_DOWN);
547  EXPECT_EQ(1, observer.GetChangedCountAndClear());
548  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
549
550  PressKey(ui::VKEY_DOWN);
551  EXPECT_EQ(0, observer.GetChangedCountAndClear());
552  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
553
554  PressKey(ui::VKEY_UP);
555  EXPECT_EQ(1, observer.GetChangedCountAndClear());
556  EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
557
558  PressKey(ui::VKEY_UP);
559  EXPECT_EQ(1, observer.GetChangedCountAndClear());
560  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
561
562  PressKey(ui::VKEY_UP);
563  EXPECT_EQ(1, observer.GetChangedCountAndClear());
564  EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
565
566  PressKey(ui::VKEY_UP);
567  EXPECT_EQ(1, observer.GetChangedCountAndClear());
568  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
569
570  PressKey(ui::VKEY_UP);
571  EXPECT_EQ(0, observer.GetChangedCountAndClear());
572  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
573
574  // Sort the table descending by column 1, view now looks like:
575  // B 5   model: 2
576  // C 2          3
577  //   3          4
578  // A 0          0
579  //   1          1
580  table_->ToggleSortOrder(0);
581  table_->ToggleSortOrder(0);
582
583  EXPECT_EQ("2 3 4 0 1", GetViewToModelAsString(table_));
584
585  table_->Select(-1);
586  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
587
588  observer.GetChangedCountAndClear();
589  // Up with nothing selected selects the first row.
590  PressKey(ui::VKEY_UP);
591  EXPECT_EQ(1, observer.GetChangedCountAndClear());
592  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
593
594  PressKey(ui::VKEY_DOWN);
595  EXPECT_EQ(1, observer.GetChangedCountAndClear());
596  EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
597
598  PressKey(ui::VKEY_DOWN);
599  EXPECT_EQ(1, observer.GetChangedCountAndClear());
600  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
601
602  PressKey(ui::VKEY_DOWN);
603  EXPECT_EQ(1, observer.GetChangedCountAndClear());
604  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
605
606  PressKey(ui::VKEY_DOWN);
607  EXPECT_EQ(1, observer.GetChangedCountAndClear());
608  EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
609
610  PressKey(ui::VKEY_DOWN);
611  EXPECT_EQ(0, observer.GetChangedCountAndClear());
612  EXPECT_EQ("active=1 anchor=1 selection=0 1", SelectionStateAsString());
613
614  PressKey(ui::VKEY_UP);
615  EXPECT_EQ(1, observer.GetChangedCountAndClear());
616  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
617
618  PressKey(ui::VKEY_UP);
619  EXPECT_EQ(1, observer.GetChangedCountAndClear());
620  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
621
622  PressKey(ui::VKEY_UP);
623  EXPECT_EQ(1, observer.GetChangedCountAndClear());
624  EXPECT_EQ("active=3 anchor=3 selection=3 4", SelectionStateAsString());
625
626  PressKey(ui::VKEY_UP);
627  EXPECT_EQ(1, observer.GetChangedCountAndClear());
628  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
629
630  PressKey(ui::VKEY_UP);
631  EXPECT_EQ(0, observer.GetChangedCountAndClear());
632  EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
633
634  table_->SetObserver(NULL);
635}
636
637// Verifies home/end do the right thing.
638TEST_F(TableViewTest, HomeEnd) {
639  // Configure the grouper so that there are three groups:
640  // A 0
641  //   1
642  // B 5
643  // C 2
644  //   3
645  model_->AddRow(2, 5, 0);
646  TableGrouperImpl grouper;
647  std::vector<int> ranges;
648  ranges.push_back(2);
649  ranges.push_back(1);
650  ranges.push_back(2);
651  grouper.SetRanges(ranges);
652  table_->SetGrouper(&grouper);
653
654  TableViewObserverImpl observer;
655  table_->SetObserver(&observer);
656
657  // Initially no selection.
658  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
659
660  PressKey(ui::VKEY_HOME);
661  EXPECT_EQ(1, observer.GetChangedCountAndClear());
662  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
663
664  PressKey(ui::VKEY_END);
665  EXPECT_EQ(1, observer.GetChangedCountAndClear());
666  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
667
668  table_->SetObserver(NULL);
669}
670
671// Verifies multiple selection gestures work (control-click, shift-click ...).
672TEST_F(TableViewTest, Multiselection) {
673  // Configure the grouper so that there are three groups:
674  // A 0
675  //   1
676  // B 5
677  // C 2
678  //   3
679  model_->AddRow(2, 5, 0);
680  TableGrouperImpl grouper;
681  std::vector<int> ranges;
682  ranges.push_back(2);
683  ranges.push_back(1);
684  ranges.push_back(2);
685  grouper.SetRanges(ranges);
686  table_->SetGrouper(&grouper);
687
688  // Initially no selection.
689  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
690
691  TableViewObserverImpl observer;
692  table_->SetObserver(&observer);
693
694  // Click on the first row, should select it and the second row.
695  ClickOnRow(0, 0);
696  EXPECT_EQ(1, observer.GetChangedCountAndClear());
697  EXPECT_EQ("active=0 anchor=0 selection=0 1", SelectionStateAsString());
698
699  // Click on the last row, should select it and the row before it.
700  ClickOnRow(4, 0);
701  EXPECT_EQ(1, observer.GetChangedCountAndClear());
702  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
703
704  // Shift click on the third row, should extend selection to it.
705  ClickOnRow(2, ui::EF_SHIFT_DOWN);
706  EXPECT_EQ(1, observer.GetChangedCountAndClear());
707  EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString());
708
709  // Control click on third row, should toggle it.
710  ClickOnRow(2, ui::EF_CONTROL_DOWN);
711  EXPECT_EQ(1, observer.GetChangedCountAndClear());
712  EXPECT_EQ("active=2 anchor=2 selection=3 4", SelectionStateAsString());
713
714  // Control-shift click on second row, should extend selection to it.
715  ClickOnRow(1, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
716  EXPECT_EQ(1, observer.GetChangedCountAndClear());
717  EXPECT_EQ("active=1 anchor=2 selection=0 1 2 3 4", SelectionStateAsString());
718
719  // Click on last row again.
720  ClickOnRow(4, 0);
721  EXPECT_EQ(1, observer.GetChangedCountAndClear());
722  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
723
724  table_->SetObserver(NULL);
725}
726
727// Verifies multiple selection gestures work when sorted.
728TEST_F(TableViewTest, MultiselectionWithSort) {
729  // Configure the grouper so that there are three groups:
730  // A 0
731  //   1
732  // B 5
733  // C 2
734  //   3
735  model_->AddRow(2, 5, 0);
736  TableGrouperImpl grouper;
737  std::vector<int> ranges;
738  ranges.push_back(2);
739  ranges.push_back(1);
740  ranges.push_back(2);
741  grouper.SetRanges(ranges);
742  table_->SetGrouper(&grouper);
743
744  // Sort the table descending by column 1, view now looks like:
745  // B 5   model: 2
746  // C 2          3
747  //   3          4
748  // A 0          0
749  //   1          1
750  table_->ToggleSortOrder(0);
751  table_->ToggleSortOrder(0);
752
753  // Initially no selection.
754  EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
755
756  TableViewObserverImpl observer;
757  table_->SetObserver(&observer);
758
759  // Click on the third row, should select it and the second row.
760  ClickOnRow(2, 0);
761  EXPECT_EQ(1, observer.GetChangedCountAndClear());
762  EXPECT_EQ("active=4 anchor=4 selection=3 4", SelectionStateAsString());
763
764  // Extend selection to first row.
765  ClickOnRow(0, ui::EF_SHIFT_DOWN);
766  EXPECT_EQ(1, observer.GetChangedCountAndClear());
767  EXPECT_EQ("active=2 anchor=4 selection=2 3 4", SelectionStateAsString());
768
769  table_->SetObserver(NULL);
770}
771
772}  // namespace views
773