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/focus/focus_manager.h"
6
7#include "base/run_loop.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/strings/utf_string_conversions.h"
10#include "ui/base/models/combobox_model.h"
11#include "ui/views/controls/button/checkbox.h"
12#include "ui/views/controls/button/label_button.h"
13#include "ui/views/controls/button/radio_button.h"
14#include "ui/views/controls/combobox/combobox.h"
15#include "ui/views/controls/label.h"
16#include "ui/views/controls/link.h"
17#include "ui/views/controls/native/native_view_host.h"
18#include "ui/views/controls/scroll_view.h"
19#include "ui/views/controls/tabbed_pane/tabbed_pane.h"
20#include "ui/views/controls/textfield/textfield.h"
21#include "ui/views/focus/accelerator_handler.h"
22#include "ui/views/focus/focus_manager_test.h"
23#include "ui/views/widget/root_view.h"
24#include "ui/views/widget/widget.h"
25
26namespace views {
27
28namespace {
29
30int count = 1;
31
32const int kTopCheckBoxID = count++;  // 1
33const int kLeftContainerID = count++;
34const int kAppleLabelID = count++;
35const int kAppleTextfieldID = count++;
36const int kOrangeLabelID = count++;  // 5
37const int kOrangeTextfieldID = count++;
38const int kBananaLabelID = count++;
39const int kBananaTextfieldID = count++;
40const int kKiwiLabelID = count++;
41const int kKiwiTextfieldID = count++;  // 10
42const int kFruitButtonID = count++;
43const int kFruitCheckBoxID = count++;
44const int kComboboxID = count++;
45
46const int kRightContainerID = count++;
47const int kAsparagusButtonID = count++;  // 15
48const int kBroccoliButtonID = count++;
49const int kCauliflowerButtonID = count++;
50
51const int kInnerContainerID = count++;
52const int kScrollViewID = count++;
53const int kRosettaLinkID = count++;  // 20
54const int kStupeurEtTremblementLinkID = count++;
55const int kDinerGameLinkID = count++;
56const int kRidiculeLinkID = count++;
57const int kClosetLinkID = count++;
58const int kVisitingLinkID = count++;  // 25
59const int kAmelieLinkID = count++;
60const int kJoyeuxNoelLinkID = count++;
61const int kCampingLinkID = count++;
62const int kBriceDeNiceLinkID = count++;
63const int kTaxiLinkID = count++;  // 30
64const int kAsterixLinkID = count++;
65
66const int kOKButtonID = count++;
67const int kCancelButtonID = count++;
68const int kHelpButtonID = count++;
69
70const int kStyleContainerID = count++;  // 35
71const int kBoldCheckBoxID = count++;
72const int kItalicCheckBoxID = count++;
73const int kUnderlinedCheckBoxID = count++;
74const int kStyleHelpLinkID = count++;
75const int kStyleTextEditID = count++;  // 40
76
77const int kSearchContainerID = count++;
78const int kSearchTextfieldID = count++;
79const int kSearchButtonID = count++;
80const int kHelpLinkID = count++;
81
82const int kThumbnailContainerID = count++;  // 45
83const int kThumbnailStarID = count++;
84const int kThumbnailSuperStarID = count++;
85
86class DummyComboboxModel : public ui::ComboboxModel {
87 public:
88  // Overridden from ui::ComboboxModel:
89  virtual int GetItemCount() const OVERRIDE { return 10; }
90  virtual string16 GetItemAt(int index) OVERRIDE {
91    return ASCIIToUTF16("Item ") + base::IntToString16(index);
92  }
93};
94
95// A View that can act as a pane.
96class PaneView : public View, public FocusTraversable {
97 public:
98  PaneView() : focus_search_(NULL) {}
99
100  // If this method is called, this view will use GetPaneFocusTraversable to
101  // have this provided FocusSearch used instead of the default one, allowing
102  // you to trap focus within the pane.
103  void EnablePaneFocus(FocusSearch* focus_search) {
104    focus_search_ = focus_search;
105  }
106
107  // Overridden from View:
108  virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE {
109    if (focus_search_)
110      return this;
111    else
112      return NULL;
113  }
114
115  // Overridden from FocusTraversable:
116  virtual views::FocusSearch* GetFocusSearch() OVERRIDE {
117    return focus_search_;
118  }
119  virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE {
120    return NULL;
121  }
122  virtual View* GetFocusTraversableParentView() OVERRIDE {
123    return NULL;
124  }
125
126 private:
127  FocusSearch* focus_search_;
128};
129
130// BorderView is a view containing a native window with its own view hierarchy.
131// It is interesting to test focus traversal from a view hierarchy to an inner
132// view hierarchy.
133class BorderView : public NativeViewHost {
134 public:
135  explicit BorderView(View* child) : child_(child), widget_(NULL) {
136    DCHECK(child);
137    set_focusable(false);
138  }
139
140  virtual ~BorderView() {}
141
142  virtual internal::RootView* GetContentsRootView() {
143    return static_cast<internal::RootView*>(widget_->GetRootView());
144  }
145
146  virtual FocusTraversable* GetFocusTraversable() OVERRIDE {
147    return static_cast<internal::RootView*>(widget_->GetRootView());
148  }
149
150  virtual void ViewHierarchyChanged(
151      const ViewHierarchyChangedDetails& details) OVERRIDE {
152    NativeViewHost::ViewHierarchyChanged(details);
153
154    if (details.child == this && details.is_add) {
155      if (!widget_) {
156        widget_ = new Widget;
157        Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
158#if defined(OS_WIN) || defined(USE_AURA)
159        params.parent = details.parent->GetWidget()->GetNativeView();
160#else
161        NOTREACHED();
162#endif
163        widget_->Init(params);
164        widget_->SetFocusTraversableParentView(this);
165        widget_->SetContentsView(child_);
166      }
167
168      // We have been added to a view hierarchy, attach the native view.
169      Attach(widget_->GetNativeView());
170      // Also update the FocusTraversable parent so the focus traversal works.
171      static_cast<internal::RootView*>(widget_->GetRootView())->
172          SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
173    }
174  }
175
176 private:
177  View* child_;
178  Widget* widget_;
179
180  DISALLOW_COPY_AND_ASSIGN(BorderView);
181};
182
183}  // namespace
184
185class FocusTraversalTest : public FocusManagerTest {
186 public:
187  virtual ~FocusTraversalTest();
188
189  virtual void InitContentView() OVERRIDE;
190
191 protected:
192  FocusTraversalTest();
193
194  View* FindViewByID(int id) {
195    View* view = GetContentsView()->GetViewByID(id);
196    if (view)
197      return view;
198    if (style_tab_)
199      view = style_tab_->GetSelectedTab()->GetViewByID(id);
200    if (view)
201      return view;
202    view = search_border_view_->GetContentsRootView()->GetViewByID(id);
203    if (view)
204      return view;
205    return NULL;
206  }
207
208 protected:
209  TabbedPane* style_tab_;
210  BorderView* search_border_view_;
211  DummyComboboxModel combobox_model_;
212  PaneView* left_container_;
213  PaneView* right_container_;
214
215  DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest);
216};
217
218FocusTraversalTest::FocusTraversalTest()
219    : style_tab_(NULL),
220      search_border_view_(NULL) {
221}
222
223FocusTraversalTest::~FocusTraversalTest() {
224}
225
226void FocusTraversalTest::InitContentView() {
227  // Create a complicated view hierarchy with lots of control types for
228  // use by all of the focus traversal tests.
229  //
230  // Class name, ID, and asterisk next to focusable views:
231  //
232  // View
233  //   Checkbox            * kTopCheckBoxID
234  //   PaneView              kLeftContainerID
235  //     Label               kAppleLabelID
236  //     Textfield         * kAppleTextfieldID
237  //     Label               kOrangeLabelID
238  //     Textfield         * kOrangeTextfieldID
239  //     Label               kBananaLabelID
240  //     Textfield         * kBananaTextfieldID
241  //     Label               kKiwiLabelID
242  //     Textfield         * kKiwiTextfieldID
243  //     NativeButton      * kFruitButtonID
244  //     Checkbox          * kFruitCheckBoxID
245  //     Combobox          * kComboboxID
246  //   PaneView              kRightContainerID
247  //     RadioButton       * kAsparagusButtonID
248  //     RadioButton       * kBroccoliButtonID
249  //     RadioButton       * kCauliflowerButtonID
250  //     View                kInnerContainerID
251  //       ScrollView        kScrollViewID
252  //         View
253  //           Link        * kRosettaLinkID
254  //           Link        * kStupeurEtTremblementLinkID
255  //           Link        * kDinerGameLinkID
256  //           Link        * kRidiculeLinkID
257  //           Link        * kClosetLinkID
258  //           Link        * kVisitingLinkID
259  //           Link        * kAmelieLinkID
260  //           Link        * kJoyeuxNoelLinkID
261  //           Link        * kCampingLinkID
262  //           Link        * kBriceDeNiceLinkID
263  //           Link        * kTaxiLinkID
264  //           Link        * kAsterixLinkID
265  //   NativeButton        * kOKButtonID
266  //   NativeButton        * kCancelButtonID
267  //   NativeButton        * kHelpButtonID
268  //   TabbedPane          * kStyleContainerID
269  //     View
270  //       Checkbox        * kBoldCheckBoxID
271  //       Checkbox        * kItalicCheckBoxID
272  //       Checkbox        * kUnderlinedCheckBoxID
273  //       Link            * kStyleHelpLinkID
274  //       Textfield       * kStyleTextEditID
275  //     Other
276  //   BorderView            kSearchContainerID
277  //     View
278  //       Textfield       * kSearchTextfieldID
279  //       NativeButton    * kSearchButtonID
280  //       Link            * kHelpLinkID
281  //   View                * kThumbnailContainerID
282  //     NativeButton      * kThumbnailStarID
283  //     NativeButton      * kThumbnailSuperStarID
284
285  GetContentsView()->set_background(
286      Background::CreateSolidBackground(SK_ColorWHITE));
287
288  Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox"));
289  GetContentsView()->AddChildView(cb);
290  // In this fast paced world, who really has time for non hard-coded layout?
291  cb->SetBounds(10, 10, 200, 20);
292  cb->set_id(kTopCheckBoxID);
293
294  left_container_ = new PaneView();
295  left_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
296  left_container_->set_background(
297      Background::CreateSolidBackground(240, 240, 240));
298  left_container_->set_id(kLeftContainerID);
299  GetContentsView()->AddChildView(left_container_);
300  left_container_->SetBounds(10, 35, 250, 200);
301
302  int label_x = 5;
303  int label_width = 50;
304  int label_height = 15;
305  int text_field_width = 150;
306  int y = 10;
307  int gap_between_labels = 10;
308
309  Label* label = new Label(ASCIIToUTF16("Apple:"));
310  label->set_id(kAppleLabelID);
311  left_container_->AddChildView(label);
312  label->SetBounds(label_x, y, label_width, label_height);
313
314  Textfield* text_field = new Textfield();
315  text_field->set_id(kAppleTextfieldID);
316  left_container_->AddChildView(text_field);
317  text_field->SetBounds(label_x + label_width + 5, y,
318                        text_field_width, label_height);
319
320  y += label_height + gap_between_labels;
321
322  label = new Label(ASCIIToUTF16("Orange:"));
323  label->set_id(kOrangeLabelID);
324  left_container_->AddChildView(label);
325  label->SetBounds(label_x, y, label_width, label_height);
326
327  text_field = new Textfield();
328  text_field->set_id(kOrangeTextfieldID);
329  left_container_->AddChildView(text_field);
330  text_field->SetBounds(label_x + label_width + 5, y,
331                        text_field_width, label_height);
332
333  y += label_height + gap_between_labels;
334
335  label = new Label(ASCIIToUTF16("Banana:"));
336  label->set_id(kBananaLabelID);
337  left_container_->AddChildView(label);
338  label->SetBounds(label_x, y, label_width, label_height);
339
340  text_field = new Textfield();
341  text_field->set_id(kBananaTextfieldID);
342  left_container_->AddChildView(text_field);
343  text_field->SetBounds(label_x + label_width + 5, y,
344                        text_field_width, label_height);
345
346  y += label_height + gap_between_labels;
347
348  label = new Label(ASCIIToUTF16("Kiwi:"));
349  label->set_id(kKiwiLabelID);
350  left_container_->AddChildView(label);
351  label->SetBounds(label_x, y, label_width, label_height);
352
353  text_field = new Textfield();
354  text_field->set_id(kKiwiTextfieldID);
355  left_container_->AddChildView(text_field);
356  text_field->SetBounds(label_x + label_width + 5, y,
357                        text_field_width, label_height);
358
359  y += label_height + gap_between_labels;
360
361  LabelButton* button = new LabelButton(NULL, ASCIIToUTF16("Click me"));
362  button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
363  button->SetBounds(label_x, y + 10, 80, 30);
364  button->set_id(kFruitButtonID);
365  left_container_->AddChildView(button);
366  y += 40;
367
368  cb =  new Checkbox(ASCIIToUTF16("This is another check box"));
369  cb->SetBounds(label_x + label_width + 5, y, 180, 20);
370  cb->set_id(kFruitCheckBoxID);
371  left_container_->AddChildView(cb);
372  y += 20;
373
374  Combobox* combobox =  new Combobox(&combobox_model_);
375  combobox->SetBounds(label_x + label_width + 5, y, 150, 30);
376  combobox->set_id(kComboboxID);
377  left_container_->AddChildView(combobox);
378
379  right_container_ = new PaneView();
380  right_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
381  right_container_->set_background(
382      Background::CreateSolidBackground(240, 240, 240));
383  right_container_->set_id(kRightContainerID);
384  GetContentsView()->AddChildView(right_container_);
385  right_container_->SetBounds(270, 35, 300, 200);
386
387  y = 10;
388  int radio_button_height = 18;
389  int gap_between_radio_buttons = 10;
390  RadioButton* radio_button = new RadioButton(ASCIIToUTF16("Asparagus"), 1);
391  radio_button->set_id(kAsparagusButtonID);
392  right_container_->AddChildView(radio_button);
393  radio_button->SetBounds(5, y, 70, radio_button_height);
394  radio_button->SetGroup(1);
395  y += radio_button_height + gap_between_radio_buttons;
396  radio_button = new RadioButton(ASCIIToUTF16("Broccoli"), 1);
397  radio_button->set_id(kBroccoliButtonID);
398  right_container_->AddChildView(radio_button);
399  radio_button->SetBounds(5, y, 70, radio_button_height);
400  radio_button->SetGroup(1);
401  RadioButton* radio_button_to_check = radio_button;
402  y += radio_button_height + gap_between_radio_buttons;
403  radio_button = new RadioButton(ASCIIToUTF16("Cauliflower"), 1);
404  radio_button->set_id(kCauliflowerButtonID);
405  right_container_->AddChildView(radio_button);
406  radio_button->SetBounds(5, y, 70, radio_button_height);
407  radio_button->SetGroup(1);
408  y += radio_button_height + gap_between_radio_buttons;
409
410  View* inner_container = new View();
411  inner_container->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
412  inner_container->set_background(
413      Background::CreateSolidBackground(230, 230, 230));
414  inner_container->set_id(kInnerContainerID);
415  right_container_->AddChildView(inner_container);
416  inner_container->SetBounds(100, 10, 150, 180);
417
418  ScrollView* scroll_view = new ScrollView();
419  scroll_view->set_id(kScrollViewID);
420  inner_container->AddChildView(scroll_view);
421  scroll_view->SetBounds(1, 1, 148, 178);
422
423  View* scroll_content = new View();
424  scroll_content->SetBounds(0, 0, 200, 200);
425  scroll_content->set_background(
426      Background::CreateSolidBackground(200, 200, 200));
427  scroll_view->SetContents(scroll_content);
428
429  static const char* const kTitles[] = {
430      "Rosetta", "Stupeur et tremblement", "The diner game",
431      "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
432      "Joyeux Noel", "Camping", "Brice de Nice",
433      "Taxi", "Asterix"
434  };
435
436  static const int kIDs[] = {
437      kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
438      kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
439      kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
440      kTaxiLinkID, kAsterixLinkID
441  };
442
443  DCHECK(arraysize(kTitles) == arraysize(kIDs));
444
445  y = 5;
446  for (size_t i = 0; i < arraysize(kTitles); ++i) {
447    Link* link = new Link(ASCIIToUTF16(kTitles[i]));
448    link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
449    link->set_id(kIDs[i]);
450    scroll_content->AddChildView(link);
451    link->SetBounds(5, y, 300, 15);
452    y += 15;
453  }
454
455  y = 250;
456  int width = 60;
457  button = new LabelButton(NULL, ASCIIToUTF16("OK"));
458  button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
459  button->set_id(kOKButtonID);
460  button->SetIsDefault(true);
461
462  GetContentsView()->AddChildView(button);
463  button->SetBounds(150, y, width, 30);
464
465  button = new LabelButton(NULL, ASCIIToUTF16("Cancel"));
466  button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
467  button->set_id(kCancelButtonID);
468  GetContentsView()->AddChildView(button);
469  button->SetBounds(220, y, width, 30);
470
471  button = new LabelButton(NULL, ASCIIToUTF16("Help"));
472  button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
473  button->set_id(kHelpButtonID);
474  GetContentsView()->AddChildView(button);
475  button->SetBounds(290, y, width, 30);
476
477  y += 40;
478
479  View* contents = NULL;
480  Link* link = NULL;
481
482  // Left bottom box with style checkboxes.
483  contents = new View();
484  contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
485  cb = new Checkbox(ASCIIToUTF16("Bold"));
486  contents->AddChildView(cb);
487  cb->SetBounds(10, 10, 50, 20);
488  cb->set_id(kBoldCheckBoxID);
489
490  cb = new Checkbox(ASCIIToUTF16("Italic"));
491  contents->AddChildView(cb);
492  cb->SetBounds(70, 10, 50, 20);
493  cb->set_id(kItalicCheckBoxID);
494
495  cb = new Checkbox(ASCIIToUTF16("Underlined"));
496  contents->AddChildView(cb);
497  cb->SetBounds(130, 10, 70, 20);
498  cb->set_id(kUnderlinedCheckBoxID);
499
500  link = new Link(ASCIIToUTF16("Help"));
501  contents->AddChildView(link);
502  link->SetBounds(10, 35, 70, 10);
503  link->set_id(kStyleHelpLinkID);
504
505  text_field = new Textfield();
506  contents->AddChildView(text_field);
507  text_field->SetBounds(10, 50, 100, 20);
508  text_field->set_id(kStyleTextEditID);
509
510  style_tab_ = new TabbedPane(false);
511  style_tab_->set_id(kStyleContainerID);
512  GetContentsView()->AddChildView(style_tab_);
513  style_tab_->SetBounds(10, y, 210, 100);
514  style_tab_->AddTab(ASCIIToUTF16("Style"), contents);
515  style_tab_->AddTab(ASCIIToUTF16("Other"), new View());
516
517  // Right bottom box with search.
518  contents = new View();
519  contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
520  text_field = new Textfield();
521  contents->AddChildView(text_field);
522  text_field->SetBounds(10, 10, 100, 20);
523  text_field->set_id(kSearchTextfieldID);
524
525  button = new LabelButton(NULL, ASCIIToUTF16("Search"));
526  button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
527  contents->AddChildView(button);
528  button->SetBounds(112, 5, 60, 30);
529  button->set_id(kSearchButtonID);
530
531  link = new Link(ASCIIToUTF16("Help"));
532  link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
533  link->set_id(kHelpLinkID);
534  contents->AddChildView(link);
535  link->SetBounds(175, 10, 30, 20);
536
537  search_border_view_ = new BorderView(contents);
538  search_border_view_->set_id(kSearchContainerID);
539
540  GetContentsView()->AddChildView(search_border_view_);
541  search_border_view_->SetBounds(300, y, 240, 50);
542
543  y += 60;
544
545  contents = new View();
546  contents->set_focusable(true);
547  contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
548  contents->set_id(kThumbnailContainerID);
549  button = new LabelButton(NULL, ASCIIToUTF16("Star"));
550  button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
551  contents->AddChildView(button);
552  button->SetBounds(5, 5, 50, 30);
553  button->set_id(kThumbnailStarID);
554  button = new LabelButton(NULL, ASCIIToUTF16("SuperStar"));
555  button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
556  contents->AddChildView(button);
557  button->SetBounds(60, 5, 100, 30);
558  button->set_id(kThumbnailSuperStarID);
559
560  GetContentsView()->AddChildView(contents);
561  contents->SetBounds(250, y, 200, 50);
562  // We can only call RadioButton::SetChecked() on the radio-button is part of
563  // the view hierarchy.
564  radio_button_to_check->SetChecked(true);
565}
566
567TEST_F(FocusTraversalTest, NormalTraversal) {
568  const int kTraversalIDs[] = { kTopCheckBoxID,  kAppleTextfieldID,
569      kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
570      kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID,
571      kRosettaLinkID, kStupeurEtTremblementLinkID,
572      kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
573      kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
574      kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
575      kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
576      kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
577      kSearchTextfieldID, kSearchButtonID, kHelpLinkID,
578      kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
579
580  // Uncomment the following line to manually test the UI of this test.
581  // base::RunLoop(new AcceleratorHandler()).Run();
582
583  // Let's traverse the whole focus hierarchy (several times, to make sure it
584  // loops OK).
585  GetFocusManager()->ClearFocus();
586  for (int i = 0; i < 3; ++i) {
587    for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
588      GetFocusManager()->AdvanceFocus(false);
589      View* focused_view = GetFocusManager()->GetFocusedView();
590      EXPECT_TRUE(focused_view != NULL);
591      if (focused_view)
592        EXPECT_EQ(kTraversalIDs[j], focused_view->id());
593    }
594  }
595
596  // Let's traverse in reverse order.
597  GetFocusManager()->ClearFocus();
598  for (int i = 0; i < 3; ++i) {
599    for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
600      GetFocusManager()->AdvanceFocus(true);
601      View* focused_view = GetFocusManager()->GetFocusedView();
602      EXPECT_TRUE(focused_view != NULL);
603      if (focused_view)
604        EXPECT_EQ(kTraversalIDs[j], focused_view->id());
605    }
606  }
607}
608
609TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) {
610  const int kDisabledIDs[] = {
611      kBananaTextfieldID, kFruitCheckBoxID, kComboboxID, kAsparagusButtonID,
612      kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID,
613      kTaxiLinkID, kAsterixLinkID, kHelpButtonID, kBoldCheckBoxID,
614      kSearchTextfieldID, kHelpLinkID };
615
616  const int kTraversalIDs[] = { kTopCheckBoxID,  kAppleTextfieldID,
617      kOrangeTextfieldID, kKiwiTextfieldID, kFruitButtonID, kBroccoliButtonID,
618      kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
619      kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID,
620      kOKButtonID, kCancelButtonID, kStyleContainerID, kItalicCheckBoxID,
621      kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
622      kSearchButtonID, kThumbnailContainerID, kThumbnailStarID,
623      kThumbnailSuperStarID };
624
625  // Let's disable some views.
626  for (size_t i = 0; i < arraysize(kDisabledIDs); i++) {
627    View* v = FindViewByID(kDisabledIDs[i]);
628    ASSERT_TRUE(v != NULL);
629    v->SetEnabled(false);
630  }
631
632  // Uncomment the following line to manually test the UI of this test.
633  // base::RunLoop(new AcceleratorHandler()).Run();
634
635  View* focused_view;
636  // Let's do one traversal (several times, to make sure it loops ok).
637  GetFocusManager()->ClearFocus();
638  for (int i = 0; i < 3; ++i) {
639    for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
640      GetFocusManager()->AdvanceFocus(false);
641      focused_view = GetFocusManager()->GetFocusedView();
642      EXPECT_TRUE(focused_view != NULL);
643      if (focused_view)
644        EXPECT_EQ(kTraversalIDs[j], focused_view->id());
645    }
646  }
647
648  // Same thing in reverse.
649  GetFocusManager()->ClearFocus();
650  for (int i = 0; i < 3; ++i) {
651    for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
652      GetFocusManager()->AdvanceFocus(true);
653      focused_view = GetFocusManager()->GetFocusedView();
654      EXPECT_TRUE(focused_view != NULL);
655      if (focused_view)
656        EXPECT_EQ(kTraversalIDs[j], focused_view->id());
657    }
658  }
659}
660
661TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) {
662  const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID,
663      kThumbnailContainerID };
664
665  const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID,
666      kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID,
667      kComboboxID, kBroccoliButtonID, kRosettaLinkID,
668      kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID,
669      kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
670      kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID,
671      kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID,
672      kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID,
673      kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID };
674
675
676  // Let's make some views invisible.
677  for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) {
678    View* v = FindViewByID(kInvisibleIDs[i]);
679    ASSERT_TRUE(v != NULL);
680    v->SetVisible(false);
681  }
682
683  // Uncomment the following line to manually test the UI of this test.
684  // base::RunLoop(new AcceleratorHandler()).Run();
685
686  View* focused_view;
687  // Let's do one traversal (several times, to make sure it loops ok).
688  GetFocusManager()->ClearFocus();
689  for (int i = 0; i < 3; ++i) {
690    for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
691      GetFocusManager()->AdvanceFocus(false);
692      focused_view = GetFocusManager()->GetFocusedView();
693      EXPECT_TRUE(focused_view != NULL);
694      if (focused_view)
695        EXPECT_EQ(kTraversalIDs[j], focused_view->id());
696    }
697  }
698
699  // Same thing in reverse.
700  GetFocusManager()->ClearFocus();
701  for (int i = 0; i < 3; ++i) {
702    for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
703      GetFocusManager()->AdvanceFocus(true);
704      focused_view = GetFocusManager()->GetFocusedView();
705      EXPECT_TRUE(focused_view != NULL);
706      if (focused_view)
707        EXPECT_EQ(kTraversalIDs[j], focused_view->id());
708    }
709  }
710}
711
712TEST_F(FocusTraversalTest, PaneTraversal) {
713  // Tests trapping the traversal within a pane - useful for full
714  // keyboard accessibility for toolbars.
715
716  // First test the left container.
717  const int kLeftTraversalIDs[] = {
718    kAppleTextfieldID,
719    kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
720    kFruitButtonID, kFruitCheckBoxID, kComboboxID };
721
722  FocusSearch focus_search_left(left_container_, true, false);
723  left_container_->EnablePaneFocus(&focus_search_left);
724  FindViewByID(kComboboxID)->RequestFocus();
725
726  // Traverse the focus hierarchy within the pane several times.
727  for (int i = 0; i < 3; ++i) {
728    for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) {
729      GetFocusManager()->AdvanceFocus(false);
730      View* focused_view = GetFocusManager()->GetFocusedView();
731      EXPECT_TRUE(focused_view != NULL);
732      if (focused_view)
733        EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
734    }
735  }
736
737  // Traverse in reverse order.
738  FindViewByID(kAppleTextfieldID)->RequestFocus();
739  for (int i = 0; i < 3; ++i) {
740    for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) {
741      GetFocusManager()->AdvanceFocus(true);
742      View* focused_view = GetFocusManager()->GetFocusedView();
743      EXPECT_TRUE(focused_view != NULL);
744      if (focused_view)
745        EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
746    }
747  }
748
749  // Now test the right container, but this time with accessibility mode.
750  // Make some links not focusable, but mark one of them as
751  // "accessibility focusable", so it should show up in the traversal.
752  const int kRightTraversalIDs[] = {
753    kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID,
754    kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
755    kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID };
756
757  FocusSearch focus_search_right(right_container_, true, true);
758  right_container_->EnablePaneFocus(&focus_search_right);
759  FindViewByID(kRosettaLinkID)->set_focusable(false);
760  FindViewByID(kStupeurEtTremblementLinkID)->set_focusable(false);
761  FindViewByID(kDinerGameLinkID)->set_accessibility_focusable(true);
762  FindViewByID(kDinerGameLinkID)->set_focusable(false);
763  FindViewByID(kAsterixLinkID)->RequestFocus();
764
765  // Traverse the focus hierarchy within the pane several times.
766  for (int i = 0; i < 3; ++i) {
767    for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) {
768      GetFocusManager()->AdvanceFocus(false);
769      View* focused_view = GetFocusManager()->GetFocusedView();
770      EXPECT_TRUE(focused_view != NULL);
771      if (focused_view)
772        EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
773    }
774  }
775
776  // Traverse in reverse order.
777  FindViewByID(kBroccoliButtonID)->RequestFocus();
778  for (int i = 0; i < 3; ++i) {
779    for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) {
780      GetFocusManager()->AdvanceFocus(true);
781      View* focused_view = GetFocusManager()->GetFocusedView();
782      EXPECT_TRUE(focused_view != NULL);
783      if (focused_view)
784        EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
785    }
786  }
787}
788
789}  // namespace views
790