1// Copyright 2013 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/combobox/combobox.h"
6
7#include <set>
8
9#include "base/basictypes.h"
10#include "base/strings/utf_string_conversions.h"
11#include "ui/base/ime/text_input_client.h"
12#include "ui/base/models/combobox_model.h"
13#include "ui/events/event.h"
14#include "ui/events/event_constants.h"
15#include "ui/events/keycodes/keyboard_codes.h"
16#include "ui/views/controls/combobox/combobox_listener.h"
17#include "ui/views/controls/menu/menu_runner.h"
18#include "ui/views/controls/menu/menu_runner_handler.h"
19#include "ui/views/ime/mock_input_method.h"
20#include "ui/views/test/menu_runner_test_api.h"
21#include "ui/views/test/views_test_base.h"
22#include "ui/views/widget/widget.h"
23
24using base::ASCIIToUTF16;
25
26namespace views {
27
28namespace {
29
30// An dummy implementation of MenuRunnerHandler to check if the dropdown menu is
31// shown or not.
32class TestMenuRunnerHandler : public MenuRunnerHandler {
33 public:
34  TestMenuRunnerHandler() : executed_(false) {}
35
36  bool executed() const { return executed_; }
37
38  virtual MenuRunner::RunResult RunMenuAt(Widget* parent,
39                                          MenuButton* button,
40                                          const gfx::Rect& bounds,
41                                          MenuAnchorPosition anchor,
42                                          ui::MenuSourceType source_type,
43                                          int32 types) OVERRIDE {
44    executed_ = true;
45    return MenuRunner::NORMAL_EXIT;
46  }
47
48 private:
49  bool executed_;
50
51  DISALLOW_COPY_AND_ASSIGN(TestMenuRunnerHandler);
52};
53
54// A wrapper of Combobox to intercept the result of OnKeyPressed() and
55// OnKeyReleased() methods.
56class TestCombobox : public Combobox {
57 public:
58  explicit TestCombobox(ui::ComboboxModel* model)
59      : Combobox(model),
60        key_handled_(false),
61        key_received_(false) {}
62
63  virtual bool OnKeyPressed(const ui::KeyEvent& e) OVERRIDE {
64    key_received_ = true;
65    key_handled_ = Combobox::OnKeyPressed(e);
66    return key_handled_;
67  }
68
69  virtual bool OnKeyReleased(const ui::KeyEvent& e) OVERRIDE {
70    key_received_ = true;
71    key_handled_ = Combobox::OnKeyReleased(e);
72    return key_handled_;
73  }
74
75  bool key_handled() const { return key_handled_; }
76  bool key_received() const { return key_received_; }
77
78  void clear() {
79    key_received_ = key_handled_ = false;
80  }
81
82 private:
83  bool key_handled_;
84  bool key_received_;
85
86  DISALLOW_COPY_AND_ASSIGN(TestCombobox);
87};
88
89// A concrete class is needed to test the combobox.
90class TestComboboxModel : public ui::ComboboxModel {
91 public:
92  TestComboboxModel() {}
93  virtual ~TestComboboxModel() {}
94
95  static const int kItemCount = 10;
96
97  // ui::ComboboxModel:
98  virtual int GetItemCount() const OVERRIDE {
99    return kItemCount;
100  }
101  virtual base::string16 GetItemAt(int index) OVERRIDE {
102    if (IsItemSeparatorAt(index)) {
103      NOTREACHED();
104      return ASCIIToUTF16("SEPARATOR");
105    }
106    return ASCIIToUTF16(index % 2 == 0 ? "PEANUT BUTTER" : "JELLY");
107  }
108  virtual bool IsItemSeparatorAt(int index) OVERRIDE {
109    return separators_.find(index) != separators_.end();
110  }
111
112  virtual int GetDefaultIndex() const OVERRIDE {
113    // Return the first index that is not a separator.
114    for (int index = 0; index < kItemCount; ++index) {
115      if (separators_.find(index) == separators_.end())
116        return index;
117    }
118    NOTREACHED();
119    return 0;
120  }
121
122  void SetSeparators(const std::set<int>& separators) {
123    separators_ = separators;
124  }
125
126 private:
127  std::set<int> separators_;
128
129  DISALLOW_COPY_AND_ASSIGN(TestComboboxModel);
130};
131
132// A combobox model which refers to a vector.
133class VectorComboboxModel : public ui::ComboboxModel {
134 public:
135  explicit VectorComboboxModel(std::vector<std::string>* values)
136      : values_(values) {}
137  virtual ~VectorComboboxModel() {}
138
139  // ui::ComboboxModel:
140  virtual int GetItemCount() const OVERRIDE {
141    return (int)values_->size();
142  }
143  virtual base::string16 GetItemAt(int index) OVERRIDE {
144    return ASCIIToUTF16(values_->at(index));
145  }
146  virtual bool IsItemSeparatorAt(int index) OVERRIDE {
147    return false;
148  }
149
150 private:
151  std::vector<std::string>* values_;
152};
153
154class EvilListener : public ComboboxListener {
155 public:
156  EvilListener() : deleted_(false) {}
157  virtual ~EvilListener() {};
158
159  // ComboboxListener:
160  virtual void OnPerformAction(Combobox* combobox) OVERRIDE {
161    delete combobox;
162    deleted_ = true;
163  }
164
165  bool deleted() const { return deleted_; }
166
167 private:
168  bool deleted_;
169
170  DISALLOW_COPY_AND_ASSIGN(EvilListener);
171};
172
173class TestComboboxListener : public views::ComboboxListener {
174 public:
175  TestComboboxListener() : perform_action_index_(-1), actions_performed_(0) {}
176  virtual ~TestComboboxListener() {}
177
178  virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE {
179    perform_action_index_ = combobox->selected_index();
180    actions_performed_++;
181  }
182
183  int perform_action_index() const {
184    return perform_action_index_;
185  }
186
187  bool on_perform_action_called() const {
188    return actions_performed_ > 0;
189  }
190
191  int actions_performed() const {
192    return actions_performed_;
193  }
194
195 private:
196  int perform_action_index_;
197  int actions_performed_;
198
199 private:
200  DISALLOW_COPY_AND_ASSIGN(TestComboboxListener);
201};
202
203}  // namespace
204
205class ComboboxTest : public ViewsTestBase {
206 public:
207  ComboboxTest() : widget_(NULL), combobox_(NULL) {}
208
209  virtual void TearDown() OVERRIDE {
210    if (widget_)
211      widget_->Close();
212    ViewsTestBase::TearDown();
213  }
214
215  void InitCombobox(const std::set<int>* separators) {
216    model_.reset(new TestComboboxModel());
217
218    if (separators)
219      model_->SetSeparators(*separators);
220
221    ASSERT_FALSE(combobox_);
222    combobox_ = new TestCombobox(model_.get());
223    combobox_->set_id(1);
224
225    widget_ = new Widget;
226    Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
227    params.bounds = gfx::Rect(200, 200, 200, 200);
228    widget_->Init(params);
229    View* container = new View();
230    widget_->SetContentsView(container);
231    container->AddChildView(combobox_);
232
233    widget_->ReplaceInputMethod(new MockInputMethod);
234
235    // Assumes the Widget is always focused.
236    widget_->GetInputMethod()->OnFocus();
237
238    combobox_->RequestFocus();
239    combobox_->SizeToPreferredSize();
240  }
241
242 protected:
243  void SendKeyEvent(ui::KeyboardCode key_code) {
244    SendKeyEventWithType(key_code, ui::ET_KEY_PRESSED);
245  }
246
247  void SendKeyEventWithType(ui::KeyboardCode key_code, ui::EventType type) {
248    ui::KeyEvent event(type, key_code, ui::EF_NONE);
249    widget_->GetInputMethod()->DispatchKeyEvent(event);
250  }
251
252  View* GetFocusedView() {
253    return widget_->GetFocusManager()->GetFocusedView();
254  }
255
256  void PerformClick(const gfx::Point& point) {
257    ui::MouseEvent pressed_event = ui::MouseEvent(ui::ET_MOUSE_PRESSED, point,
258                                                  point,
259                                                  ui::EF_LEFT_MOUSE_BUTTON,
260                                                  ui::EF_LEFT_MOUSE_BUTTON);
261    widget_->OnMouseEvent(&pressed_event);
262    ui::MouseEvent released_event = ui::MouseEvent(ui::ET_MOUSE_RELEASED, point,
263                                                   point,
264                                                   ui::EF_LEFT_MOUSE_BUTTON,
265                                                   ui::EF_LEFT_MOUSE_BUTTON);
266    widget_->OnMouseEvent(&released_event);
267  }
268
269  // We need widget to populate wrapper class.
270  Widget* widget_;
271
272  // |combobox_| will be allocated InitCombobox() and then owned by |widget_|.
273  TestCombobox* combobox_;
274
275  // Combobox does not take ownership of the model, hence it needs to be scoped.
276  scoped_ptr<TestComboboxModel> model_;
277};
278
279TEST_F(ComboboxTest, KeyTest) {
280  InitCombobox(NULL);
281  SendKeyEvent(ui::VKEY_END);
282  EXPECT_EQ(combobox_->selected_index() + 1, model_->GetItemCount());
283  SendKeyEvent(ui::VKEY_HOME);
284  EXPECT_EQ(combobox_->selected_index(), 0);
285  SendKeyEvent(ui::VKEY_DOWN);
286  SendKeyEvent(ui::VKEY_DOWN);
287  EXPECT_EQ(combobox_->selected_index(), 2);
288  SendKeyEvent(ui::VKEY_RIGHT);
289  EXPECT_EQ(combobox_->selected_index(), 2);
290  SendKeyEvent(ui::VKEY_LEFT);
291  EXPECT_EQ(combobox_->selected_index(), 2);
292  SendKeyEvent(ui::VKEY_UP);
293  EXPECT_EQ(combobox_->selected_index(), 1);
294  SendKeyEvent(ui::VKEY_PRIOR);
295  EXPECT_EQ(combobox_->selected_index(), 0);
296  SendKeyEvent(ui::VKEY_NEXT);
297  EXPECT_EQ(combobox_->selected_index(), model_->GetItemCount() - 1);
298}
299
300// Check that if a combobox is disabled before it has a native wrapper, then the
301// native wrapper inherits the disabled state when it gets created.
302TEST_F(ComboboxTest, DisabilityTest) {
303  model_.reset(new TestComboboxModel());
304
305  ASSERT_FALSE(combobox_);
306  combobox_ = new TestCombobox(model_.get());
307  combobox_->SetEnabled(false);
308
309  widget_ = new Widget;
310  Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
311  params.bounds = gfx::Rect(100, 100, 100, 100);
312  widget_->Init(params);
313  View* container = new View();
314  widget_->SetContentsView(container);
315  container->AddChildView(combobox_);
316  EXPECT_FALSE(combobox_->enabled());
317}
318
319// Verifies that we don't select a separator line in combobox when navigating
320// through keyboard.
321TEST_F(ComboboxTest, SkipSeparatorSimple) {
322  std::set<int> separators;
323  separators.insert(2);
324  InitCombobox(&separators);
325  EXPECT_EQ(0, combobox_->selected_index());
326  SendKeyEvent(ui::VKEY_DOWN);
327  EXPECT_EQ(1, combobox_->selected_index());
328  SendKeyEvent(ui::VKEY_DOWN);
329  EXPECT_EQ(3, combobox_->selected_index());
330  SendKeyEvent(ui::VKEY_UP);
331  EXPECT_EQ(1, combobox_->selected_index());
332  SendKeyEvent(ui::VKEY_HOME);
333  EXPECT_EQ(0, combobox_->selected_index());
334  SendKeyEvent(ui::VKEY_PRIOR);
335  EXPECT_EQ(0, combobox_->selected_index());
336  SendKeyEvent(ui::VKEY_END);
337  EXPECT_EQ(9, combobox_->selected_index());
338}
339
340// Verifies that we never select the separator that is in the beginning of the
341// combobox list when navigating through keyboard.
342TEST_F(ComboboxTest, SkipSeparatorBeginning) {
343  std::set<int> separators;
344  separators.insert(0);
345  InitCombobox(&separators);
346  EXPECT_EQ(1, combobox_->selected_index());
347  SendKeyEvent(ui::VKEY_DOWN);
348  EXPECT_EQ(2, combobox_->selected_index());
349  SendKeyEvent(ui::VKEY_DOWN);
350  EXPECT_EQ(3, combobox_->selected_index());
351  SendKeyEvent(ui::VKEY_UP);
352  EXPECT_EQ(2, combobox_->selected_index());
353  SendKeyEvent(ui::VKEY_HOME);
354  EXPECT_EQ(1, combobox_->selected_index());
355  SendKeyEvent(ui::VKEY_PRIOR);
356  EXPECT_EQ(1, combobox_->selected_index());
357  SendKeyEvent(ui::VKEY_END);
358  EXPECT_EQ(9, combobox_->selected_index());
359}
360
361// Verifies that we never select the separator that is in the end of the
362// combobox list when navigating through keyboard.
363TEST_F(ComboboxTest, SkipSeparatorEnd) {
364  std::set<int> separators;
365  separators.insert(TestComboboxModel::kItemCount - 1);
366  InitCombobox(&separators);
367  combobox_->SetSelectedIndex(8);
368  SendKeyEvent(ui::VKEY_DOWN);
369  EXPECT_EQ(8, combobox_->selected_index());
370  SendKeyEvent(ui::VKEY_UP);
371  EXPECT_EQ(7, combobox_->selected_index());
372  SendKeyEvent(ui::VKEY_END);
373  EXPECT_EQ(8, combobox_->selected_index());
374}
375
376// Verifies that we never select any of the adjacent separators (multiple
377// consecutive) that appear in the beginning of the combobox list when
378// navigating through keyboard.
379TEST_F(ComboboxTest, SkipMultipleSeparatorsAtBeginning) {
380  std::set<int> separators;
381  separators.insert(0);
382  separators.insert(1);
383  separators.insert(2);
384  InitCombobox(&separators);
385  EXPECT_EQ(3, combobox_->selected_index());
386  SendKeyEvent(ui::VKEY_DOWN);
387  EXPECT_EQ(4, combobox_->selected_index());
388  SendKeyEvent(ui::VKEY_UP);
389  EXPECT_EQ(3, combobox_->selected_index());
390  SendKeyEvent(ui::VKEY_NEXT);
391  EXPECT_EQ(9, combobox_->selected_index());
392  SendKeyEvent(ui::VKEY_HOME);
393  EXPECT_EQ(3, combobox_->selected_index());
394  SendKeyEvent(ui::VKEY_END);
395  EXPECT_EQ(9, combobox_->selected_index());
396  SendKeyEvent(ui::VKEY_PRIOR);
397  EXPECT_EQ(3, combobox_->selected_index());
398}
399
400// Verifies that we never select any of the adjacent separators (multiple
401// consecutive) that appear in the middle of the combobox list when navigating
402// through keyboard.
403TEST_F(ComboboxTest, SkipMultipleAdjacentSeparatorsAtMiddle) {
404  std::set<int> separators;
405  separators.insert(4);
406  separators.insert(5);
407  separators.insert(6);
408  InitCombobox(&separators);
409  combobox_->SetSelectedIndex(3);
410  SendKeyEvent(ui::VKEY_DOWN);
411  EXPECT_EQ(7, combobox_->selected_index());
412  SendKeyEvent(ui::VKEY_UP);
413  EXPECT_EQ(3, combobox_->selected_index());
414}
415
416// Verifies that we never select any of the adjacent separators (multiple
417// consecutive) that appear in the end of the combobox list when navigating
418// through keyboard.
419TEST_F(ComboboxTest, SkipMultipleSeparatorsAtEnd) {
420  std::set<int> separators;
421  separators.insert(7);
422  separators.insert(8);
423  separators.insert(9);
424  InitCombobox(&separators);
425  combobox_->SetSelectedIndex(6);
426  SendKeyEvent(ui::VKEY_DOWN);
427  EXPECT_EQ(6, combobox_->selected_index());
428  SendKeyEvent(ui::VKEY_UP);
429  EXPECT_EQ(5, combobox_->selected_index());
430  SendKeyEvent(ui::VKEY_HOME);
431  EXPECT_EQ(0, combobox_->selected_index());
432  SendKeyEvent(ui::VKEY_NEXT);
433  EXPECT_EQ(6, combobox_->selected_index());
434  SendKeyEvent(ui::VKEY_PRIOR);
435  EXPECT_EQ(0, combobox_->selected_index());
436  SendKeyEvent(ui::VKEY_END);
437  EXPECT_EQ(6, combobox_->selected_index());
438}
439
440TEST_F(ComboboxTest, GetTextForRowTest) {
441  std::set<int> separators;
442  separators.insert(0);
443  separators.insert(1);
444  separators.insert(9);
445  InitCombobox(&separators);
446  for (int i = 0; i < combobox_->GetRowCount(); ++i) {
447    if (separators.count(i) != 0) {
448      EXPECT_TRUE(combobox_->GetTextForRow(i).empty()) << i;
449    } else {
450      EXPECT_EQ(ASCIIToUTF16(i % 2 == 0 ? "PEANUT BUTTER" : "JELLY"),
451                combobox_->GetTextForRow(i)) << i;
452    }
453  }
454}
455
456// Verifies selecting the first matching value (and returning whether found).
457TEST_F(ComboboxTest, SelectValue) {
458  InitCombobox(NULL);
459  ASSERT_EQ(model_->GetDefaultIndex(), combobox_->selected_index());
460  EXPECT_TRUE(combobox_->SelectValue(ASCIIToUTF16("PEANUT BUTTER")));
461  EXPECT_EQ(0, combobox_->selected_index());
462  EXPECT_TRUE(combobox_->SelectValue(ASCIIToUTF16("JELLY")));
463  EXPECT_EQ(1, combobox_->selected_index());
464  EXPECT_FALSE(combobox_->SelectValue(ASCIIToUTF16("BANANAS")));
465  EXPECT_EQ(1, combobox_->selected_index());
466
467  // With the action style, the selected index is always 0.
468  combobox_->SetStyle(Combobox::STYLE_ACTION);
469  EXPECT_FALSE(combobox_->SelectValue(ASCIIToUTF16("PEANUT BUTTER")));
470  EXPECT_EQ(0, combobox_->selected_index());
471  EXPECT_FALSE(combobox_->SelectValue(ASCIIToUTF16("JELLY")));
472  EXPECT_EQ(0, combobox_->selected_index());
473  EXPECT_FALSE(combobox_->SelectValue(ASCIIToUTF16("BANANAS")));
474  EXPECT_EQ(0, combobox_->selected_index());
475}
476
477TEST_F(ComboboxTest, SelectIndexActionStyle) {
478  InitCombobox(NULL);
479
480  // With the action style, the selected index is always 0.
481  combobox_->SetStyle(Combobox::STYLE_ACTION);
482  combobox_->SetSelectedIndex(1);
483  EXPECT_EQ(0, combobox_->selected_index());
484  combobox_->SetSelectedIndex(2);
485  EXPECT_EQ(0, combobox_->selected_index());
486  combobox_->SetSelectedIndex(3);
487  EXPECT_EQ(0, combobox_->selected_index());
488}
489
490TEST_F(ComboboxTest, ListenerHandlesDelete) {
491  TestComboboxModel model;
492
493  // |combobox| will be deleted on change.
494  TestCombobox* combobox = new TestCombobox(&model);
495  scoped_ptr<EvilListener> evil_listener(new EvilListener());
496  combobox->set_listener(evil_listener.get());
497  ASSERT_NO_FATAL_FAILURE(combobox->ExecuteCommand(2));
498  EXPECT_TRUE(evil_listener->deleted());
499
500  // With STYLE_ACTION
501  // |combobox| will be deleted on change.
502  combobox = new TestCombobox(&model);
503  evil_listener.reset(new EvilListener());
504  combobox->set_listener(evil_listener.get());
505  combobox->SetStyle(Combobox::STYLE_ACTION);
506  ASSERT_NO_FATAL_FAILURE(combobox->ExecuteCommand(2));
507  EXPECT_TRUE(evil_listener->deleted());
508}
509
510TEST_F(ComboboxTest, Click) {
511  InitCombobox(NULL);
512
513  TestComboboxListener listener;
514  combobox_->set_listener(&listener);
515
516  combobox_->Layout();
517
518  // Click the left side. The menu is shown.
519  TestMenuRunnerHandler* test_menu_runner_handler = new TestMenuRunnerHandler();
520  scoped_ptr<MenuRunnerHandler> menu_runner_handler(test_menu_runner_handler);
521  test::MenuRunnerTestAPI test_api(
522      combobox_->dropdown_list_menu_runner_.get());
523  test_api.SetMenuRunnerHandler(menu_runner_handler.Pass());
524  PerformClick(gfx::Point(combobox_->x() + 1,
525                          combobox_->y() + combobox_->height() / 2));
526  EXPECT_FALSE(listener.on_perform_action_called());
527  EXPECT_TRUE(test_menu_runner_handler->executed());
528}
529
530TEST_F(ComboboxTest, ClickButDisabled) {
531  InitCombobox(NULL);
532
533  TestComboboxListener listener;
534  combobox_->set_listener(&listener);
535
536  combobox_->Layout();
537  combobox_->SetEnabled(false);
538
539  // Click the left side, but nothing happens since the combobox is disabled.
540  TestMenuRunnerHandler* test_menu_runner_handler = new TestMenuRunnerHandler();
541  scoped_ptr<MenuRunnerHandler> menu_runner_handler(test_menu_runner_handler);
542  test::MenuRunnerTestAPI test_api(
543      combobox_->dropdown_list_menu_runner_.get());
544  test_api.SetMenuRunnerHandler(menu_runner_handler.Pass());
545  PerformClick(gfx::Point(combobox_->x() + 1,
546                          combobox_->y() + combobox_->height() / 2));
547  EXPECT_FALSE(listener.on_perform_action_called());
548  EXPECT_FALSE(test_menu_runner_handler->executed());
549}
550
551TEST_F(ComboboxTest, NotifyOnClickWithReturnKey) {
552  InitCombobox(NULL);
553
554  TestComboboxListener listener;
555  combobox_->set_listener(&listener);
556
557  // With STYLE_NORMAL, the click event is ignored.
558  SendKeyEvent(ui::VKEY_RETURN);
559  EXPECT_FALSE(listener.on_perform_action_called());
560
561  // With STYLE_ACTION, the click event is notified.
562  combobox_->SetStyle(Combobox::STYLE_ACTION);
563  SendKeyEvent(ui::VKEY_RETURN);
564  EXPECT_TRUE(listener.on_perform_action_called());
565  EXPECT_EQ(0, listener.perform_action_index());
566}
567
568TEST_F(ComboboxTest, NotifyOnClickWithSpaceKey) {
569  InitCombobox(NULL);
570
571  TestComboboxListener listener;
572  combobox_->set_listener(&listener);
573
574  // With STYLE_NORMAL, the click event is ignored.
575  SendKeyEvent(ui::VKEY_SPACE);
576  EXPECT_FALSE(listener.on_perform_action_called());
577  SendKeyEventWithType(ui::VKEY_SPACE, ui::ET_KEY_RELEASED);
578  EXPECT_FALSE(listener.on_perform_action_called());
579
580  // With STYLE_ACTION, the click event is notified after releasing.
581  combobox_->SetStyle(Combobox::STYLE_ACTION);
582  SendKeyEvent(ui::VKEY_SPACE);
583  EXPECT_FALSE(listener.on_perform_action_called());
584  SendKeyEventWithType(ui::VKEY_SPACE, ui::ET_KEY_RELEASED);
585  EXPECT_TRUE(listener.on_perform_action_called());
586  EXPECT_EQ(0, listener.perform_action_index());
587}
588
589TEST_F(ComboboxTest, NotifyOnClickWithMouse) {
590  InitCombobox(NULL);
591
592  TestComboboxListener listener;
593  combobox_->set_listener(&listener);
594
595  combobox_->SetStyle(Combobox::STYLE_ACTION);
596  combobox_->Layout();
597
598  // Click the right side (arrow button). The menu is shown.
599  TestMenuRunnerHandler* test_menu_runner_handler = new TestMenuRunnerHandler();
600  scoped_ptr<MenuRunnerHandler> menu_runner_handler(test_menu_runner_handler);
601  scoped_ptr<test::MenuRunnerTestAPI> test_api(
602      new test::MenuRunnerTestAPI(combobox_->dropdown_list_menu_runner_.get()));
603  test_api->SetMenuRunnerHandler(menu_runner_handler.Pass());
604
605  PerformClick(gfx::Point(combobox_->x() + combobox_->width() - 1,
606                          combobox_->y() + combobox_->height() / 2));
607  EXPECT_FALSE(listener.on_perform_action_called());
608  EXPECT_TRUE(test_menu_runner_handler->executed());
609
610  // Click the left side (text button). The click event is notified.
611  test_menu_runner_handler = new TestMenuRunnerHandler();
612  menu_runner_handler.reset(test_menu_runner_handler);
613  test_api.reset(
614      new test::MenuRunnerTestAPI(combobox_->dropdown_list_menu_runner_.get()));
615  test_api->SetMenuRunnerHandler(menu_runner_handler.Pass());
616  PerformClick(gfx::Point(combobox_->x() + 1,
617                          combobox_->y() + combobox_->height() / 2));
618  EXPECT_TRUE(listener.on_perform_action_called());
619  EXPECT_FALSE(test_menu_runner_handler->executed());
620  EXPECT_EQ(0, listener.perform_action_index());
621}
622
623TEST_F(ComboboxTest, ConsumingPressKeyEvents) {
624  InitCombobox(NULL);
625
626  EXPECT_FALSE(combobox_->OnKeyPressed(
627      ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE)));
628  EXPECT_FALSE(combobox_->OnKeyPressed(
629      ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE, ui::EF_NONE)));
630
631  // When the combobox's style is STYLE_ACTION, pressing events of a space key
632  // or an enter key will be consumed.
633  combobox_->SetStyle(Combobox::STYLE_ACTION);
634  EXPECT_TRUE(combobox_->OnKeyPressed(
635      ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE)));
636  EXPECT_TRUE(combobox_->OnKeyPressed(
637      ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE, ui::EF_NONE)));
638}
639
640TEST_F(ComboboxTest, ContentWidth) {
641  std::vector<std::string> values;
642  VectorComboboxModel model(&values);
643  TestCombobox combobox(&model);
644
645  std::string long_item = "this is the long item";
646  std::string short_item = "s";
647
648  values.resize(1);
649  values[0] = long_item;
650  combobox.ModelChanged();
651
652  const int long_item_width = combobox.content_size_.width();
653
654  values[0] = short_item;
655  combobox.ModelChanged();
656
657  const int short_item_width = combobox.content_size_.width();
658
659  values.resize(2);
660  values[0] = short_item;
661  values[1] = long_item;
662  combobox.ModelChanged();
663
664  // When the style is STYLE_NORMAL, the width will fit with the longest item.
665  combobox.SetStyle(Combobox::STYLE_NORMAL);
666  EXPECT_EQ(long_item_width, combobox.content_size_.width());
667
668  // When the style is STYLE_ACTION, the width will fit with the first items'
669  // width.
670  combobox.SetStyle(Combobox::STYLE_ACTION);
671  EXPECT_EQ(short_item_width, combobox.content_size_.width());
672}
673
674TEST_F(ComboboxTest, TypingPrefixNotifiesListener) {
675  InitCombobox(NULL);
676
677  TestComboboxListener listener;
678  combobox_->set_listener(&listener);
679
680  // Type the first character of the second menu item ("JELLY").
681  combobox_->GetTextInputClient()->InsertChar('J', ui::EF_NONE);
682  EXPECT_EQ(1, listener.actions_performed());
683  EXPECT_EQ(1, listener.perform_action_index());
684
685  // Type the second character of "JELLY", item shouldn't change and
686  // OnPerformAction() shouldn't be re-called.
687  combobox_->GetTextInputClient()->InsertChar('E', ui::EF_NONE);
688  EXPECT_EQ(1, listener.actions_performed());
689  EXPECT_EQ(1, listener.perform_action_index());
690
691  // Clears the typed text.
692  combobox_->OnBlur();
693
694  // Type the first character of "PEANUT BUTTER", which should change the
695  // selected index and perform an action.
696  combobox_->GetTextInputClient()->InsertChar('P', ui::EF_NONE);
697  EXPECT_EQ(2, listener.actions_performed());
698  EXPECT_EQ(2, listener.perform_action_index());
699}
700
701}  // namespace views
702