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.
5 #include "ui/views/focus/focus_manager.h"
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/background.h"
12 #include "ui/views/border.h"
13 #include "ui/views/controls/button/checkbox.h"
14 #include "ui/views/controls/button/label_button.h"
15 #include "ui/views/controls/button/radio_button.h"
16 #include "ui/views/controls/combobox/combobox.h"
17 #include "ui/views/controls/label.h"
18 #include "ui/views/controls/link.h"
19 #include "ui/views/controls/native/native_view_host.h"
20 #include "ui/views/controls/scroll_view.h"
21 #include "ui/views/controls/tabbed_pane/tabbed_pane.h"
22 #include "ui/views/controls/textfield/textfield.h"
23 #include "ui/views/focus/focus_manager_test.h"
24 #include "ui/views/widget/root_view.h"
25 #include "ui/views/widget/widget.h"
27 using base::ASCIIToUTF16;
35 const int kTopCheckBoxID = count++; // 1
36 const int kLeftContainerID = count++;
37 const int kAppleLabelID = count++;
38 const int kAppleTextfieldID = count++;
39 const int kOrangeLabelID = count++; // 5
40 const int kOrangeTextfieldID = count++;
41 const int kBananaLabelID = count++;
42 const int kBananaTextfieldID = count++;
43 const int kKiwiLabelID = count++;
44 const int kKiwiTextfieldID = count++; // 10
45 const int kFruitButtonID = count++;
46 const int kFruitCheckBoxID = count++;
47 const int kComboboxID = count++;
49 const int kRightContainerID = count++;
50 const int kAsparagusButtonID = count++; // 15
51 const int kBroccoliButtonID = count++;
52 const int kCauliflowerButtonID = count++;
54 const int kInnerContainerID = count++;
55 const int kScrollViewID = count++;
56 const int kRosettaLinkID = count++; // 20
57 const int kStupeurEtTremblementLinkID = count++;
58 const int kDinerGameLinkID = count++;
59 const int kRidiculeLinkID = count++;
60 const int kClosetLinkID = count++;
61 const int kVisitingLinkID = count++; // 25
62 const int kAmelieLinkID = count++;
63 const int kJoyeuxNoelLinkID = count++;
64 const int kCampingLinkID = count++;
65 const int kBriceDeNiceLinkID = count++;
66 const int kTaxiLinkID = count++; // 30
67 const int kAsterixLinkID = count++;
69 const int kOKButtonID = count++;
70 const int kCancelButtonID = count++;
71 const int kHelpButtonID = count++;
73 const int kStyleContainerID = count++; // 35
74 const int kBoldCheckBoxID = count++;
75 const int kItalicCheckBoxID = count++;
76 const int kUnderlinedCheckBoxID = count++;
77 const int kStyleHelpLinkID = count++;
78 const int kStyleTextEditID = count++; // 40
80 const int kSearchContainerID = count++;
81 const int kSearchTextfieldID = count++;
82 const int kSearchButtonID = count++;
83 const int kHelpLinkID = count++;
85 const int kThumbnailContainerID = count++; // 45
86 const int kThumbnailStarID = count++;
87 const int kThumbnailSuperStarID = count++;
89 class DummyComboboxModel : public ui::ComboboxModel {
91 // Overridden from ui::ComboboxModel:
92 virtual int GetItemCount() const OVERRIDE { return 10; }
93 virtual base::string16 GetItemAt(int index) OVERRIDE {
94 return ASCIIToUTF16("Item ") + base::IntToString16(index);
98 // A View that can act as a pane.
99 class PaneView : public View, public FocusTraversable {
101 PaneView() : focus_search_(NULL) {}
103 // If this method is called, this view will use GetPaneFocusTraversable to
104 // have this provided FocusSearch used instead of the default one, allowing
105 // you to trap focus within the pane.
106 void EnablePaneFocus(FocusSearch* focus_search) {
107 focus_search_ = focus_search;
110 // Overridden from View:
111 virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE {
118 // Overridden from FocusTraversable:
119 virtual views::FocusSearch* GetFocusSearch() OVERRIDE {
120 return focus_search_;
122 virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE {
125 virtual View* GetFocusTraversableParentView() OVERRIDE {
130 FocusSearch* focus_search_;
133 // BorderView is a view containing a native window with its own view hierarchy.
134 // It is interesting to test focus traversal from a view hierarchy to an inner
136 class BorderView : public NativeViewHost {
138 explicit BorderView(View* child) : child_(child), widget_(NULL) {
143 virtual ~BorderView() {}
145 virtual internal::RootView* GetContentsRootView() {
146 return static_cast<internal::RootView*>(widget_->GetRootView());
149 virtual FocusTraversable* GetFocusTraversable() OVERRIDE {
150 return static_cast<internal::RootView*>(widget_->GetRootView());
153 virtual void ViewHierarchyChanged(
154 const ViewHierarchyChangedDetails& details) OVERRIDE {
155 NativeViewHost::ViewHierarchyChanged(details);
157 if (details.child == this && details.is_add) {
159 widget_ = new Widget;
160 Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
161 #if defined(OS_WIN) || defined(USE_AURA)
162 params.parent = details.parent->GetWidget()->GetNativeView();
166 widget_->Init(params);
167 widget_->SetFocusTraversableParentView(this);
168 widget_->SetContentsView(child_);
171 // We have been added to a view hierarchy, attach the native view.
172 Attach(widget_->GetNativeView());
173 // Also update the FocusTraversable parent so the focus traversal works.
174 static_cast<internal::RootView*>(widget_->GetRootView())->
175 SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
183 DISALLOW_COPY_AND_ASSIGN(BorderView);
188 class FocusTraversalTest : public FocusManagerTest {
190 virtual ~FocusTraversalTest();
192 virtual void InitContentView() OVERRIDE;
195 FocusTraversalTest();
197 View* FindViewByID(int id) {
198 View* view = GetContentsView()->GetViewByID(id);
202 view = style_tab_->GetSelectedTab()->GetViewByID(id);
205 view = search_border_view_->GetContentsRootView()->GetViewByID(id);
212 TabbedPane* style_tab_;
213 BorderView* search_border_view_;
214 DummyComboboxModel combobox_model_;
215 PaneView* left_container_;
216 PaneView* right_container_;
218 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest);
221 FocusTraversalTest::FocusTraversalTest()
223 search_border_view_(NULL) {
226 FocusTraversalTest::~FocusTraversalTest() {
229 void FocusTraversalTest::InitContentView() {
230 // Create a complicated view hierarchy with lots of control types for
231 // use by all of the focus traversal tests.
233 // Class name, ID, and asterisk next to focusable views:
236 // Checkbox * kTopCheckBoxID
237 // PaneView kLeftContainerID
238 // Label kAppleLabelID
239 // Textfield * kAppleTextfieldID
240 // Label kOrangeLabelID
241 // Textfield * kOrangeTextfieldID
242 // Label kBananaLabelID
243 // Textfield * kBananaTextfieldID
244 // Label kKiwiLabelID
245 // Textfield * kKiwiTextfieldID
246 // NativeButton * kFruitButtonID
247 // Checkbox * kFruitCheckBoxID
248 // Combobox * kComboboxID
249 // PaneView kRightContainerID
250 // RadioButton * kAsparagusButtonID
251 // RadioButton * kBroccoliButtonID
252 // RadioButton * kCauliflowerButtonID
253 // View kInnerContainerID
254 // ScrollView kScrollViewID
256 // Link * kRosettaLinkID
257 // Link * kStupeurEtTremblementLinkID
258 // Link * kDinerGameLinkID
259 // Link * kRidiculeLinkID
260 // Link * kClosetLinkID
261 // Link * kVisitingLinkID
262 // Link * kAmelieLinkID
263 // Link * kJoyeuxNoelLinkID
264 // Link * kCampingLinkID
265 // Link * kBriceDeNiceLinkID
266 // Link * kTaxiLinkID
267 // Link * kAsterixLinkID
268 // NativeButton * kOKButtonID
269 // NativeButton * kCancelButtonID
270 // NativeButton * kHelpButtonID
271 // TabbedPane * kStyleContainerID
273 // Checkbox * kBoldCheckBoxID
274 // Checkbox * kItalicCheckBoxID
275 // Checkbox * kUnderlinedCheckBoxID
276 // Link * kStyleHelpLinkID
277 // Textfield * kStyleTextEditID
279 // BorderView kSearchContainerID
281 // Textfield * kSearchTextfieldID
282 // NativeButton * kSearchButtonID
283 // Link * kHelpLinkID
284 // View * kThumbnailContainerID
285 // NativeButton * kThumbnailStarID
286 // NativeButton * kThumbnailSuperStarID
288 GetContentsView()->set_background(
289 Background::CreateSolidBackground(SK_ColorWHITE));
291 Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox"));
292 GetContentsView()->AddChildView(cb);
293 // In this fast paced world, who really has time for non hard-coded layout?
294 cb->SetBounds(10, 10, 200, 20);
295 cb->set_id(kTopCheckBoxID);
297 left_container_ = new PaneView();
298 left_container_->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
299 left_container_->set_background(
300 Background::CreateSolidBackground(240, 240, 240));
301 left_container_->set_id(kLeftContainerID);
302 GetContentsView()->AddChildView(left_container_);
303 left_container_->SetBounds(10, 35, 250, 200);
306 int label_width = 50;
307 int label_height = 15;
308 int text_field_width = 150;
310 int gap_between_labels = 10;
312 Label* label = new Label(ASCIIToUTF16("Apple:"));
313 label->set_id(kAppleLabelID);
314 left_container_->AddChildView(label);
315 label->SetBounds(label_x, y, label_width, label_height);
317 Textfield* text_field = new Textfield();
318 text_field->set_id(kAppleTextfieldID);
319 left_container_->AddChildView(text_field);
320 text_field->SetBounds(label_x + label_width + 5, y,
321 text_field_width, label_height);
323 y += label_height + gap_between_labels;
325 label = new Label(ASCIIToUTF16("Orange:"));
326 label->set_id(kOrangeLabelID);
327 left_container_->AddChildView(label);
328 label->SetBounds(label_x, y, label_width, label_height);
330 text_field = new Textfield();
331 text_field->set_id(kOrangeTextfieldID);
332 left_container_->AddChildView(text_field);
333 text_field->SetBounds(label_x + label_width + 5, y,
334 text_field_width, label_height);
336 y += label_height + gap_between_labels;
338 label = new Label(ASCIIToUTF16("Banana:"));
339 label->set_id(kBananaLabelID);
340 left_container_->AddChildView(label);
341 label->SetBounds(label_x, y, label_width, label_height);
343 text_field = new Textfield();
344 text_field->set_id(kBananaTextfieldID);
345 left_container_->AddChildView(text_field);
346 text_field->SetBounds(label_x + label_width + 5, y,
347 text_field_width, label_height);
349 y += label_height + gap_between_labels;
351 label = new Label(ASCIIToUTF16("Kiwi:"));
352 label->set_id(kKiwiLabelID);
353 left_container_->AddChildView(label);
354 label->SetBounds(label_x, y, label_width, label_height);
356 text_field = new Textfield();
357 text_field->set_id(kKiwiTextfieldID);
358 left_container_->AddChildView(text_field);
359 text_field->SetBounds(label_x + label_width + 5, y,
360 text_field_width, label_height);
362 y += label_height + gap_between_labels;
364 LabelButton* button = new LabelButton(NULL, ASCIIToUTF16("Click me"));
365 button->SetStyle(Button::STYLE_BUTTON);
366 button->SetBounds(label_x, y + 10, 80, 30);
367 button->set_id(kFruitButtonID);
368 left_container_->AddChildView(button);
371 cb = new Checkbox(ASCIIToUTF16("This is another check box"));
372 cb->SetBounds(label_x + label_width + 5, y, 180, 20);
373 cb->set_id(kFruitCheckBoxID);
374 left_container_->AddChildView(cb);
377 Combobox* combobox = new Combobox(&combobox_model_);
378 combobox->SetBounds(label_x + label_width + 5, y, 150, 30);
379 combobox->set_id(kComboboxID);
380 left_container_->AddChildView(combobox);
382 right_container_ = new PaneView();
383 right_container_->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
384 right_container_->set_background(
385 Background::CreateSolidBackground(240, 240, 240));
386 right_container_->set_id(kRightContainerID);
387 GetContentsView()->AddChildView(right_container_);
388 right_container_->SetBounds(270, 35, 300, 200);
391 int radio_button_height = 18;
392 int gap_between_radio_buttons = 10;
393 RadioButton* radio_button = new RadioButton(ASCIIToUTF16("Asparagus"), 1);
394 radio_button->set_id(kAsparagusButtonID);
395 right_container_->AddChildView(radio_button);
396 radio_button->SetBounds(5, y, 70, radio_button_height);
397 radio_button->SetGroup(1);
398 y += radio_button_height + gap_between_radio_buttons;
399 radio_button = new RadioButton(ASCIIToUTF16("Broccoli"), 1);
400 radio_button->set_id(kBroccoliButtonID);
401 right_container_->AddChildView(radio_button);
402 radio_button->SetBounds(5, y, 70, radio_button_height);
403 radio_button->SetGroup(1);
404 RadioButton* radio_button_to_check = radio_button;
405 y += radio_button_height + gap_between_radio_buttons;
406 radio_button = new RadioButton(ASCIIToUTF16("Cauliflower"), 1);
407 radio_button->set_id(kCauliflowerButtonID);
408 right_container_->AddChildView(radio_button);
409 radio_button->SetBounds(5, y, 70, radio_button_height);
410 radio_button->SetGroup(1);
411 y += radio_button_height + gap_between_radio_buttons;
413 View* inner_container = new View();
414 inner_container->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
415 inner_container->set_background(
416 Background::CreateSolidBackground(230, 230, 230));
417 inner_container->set_id(kInnerContainerID);
418 right_container_->AddChildView(inner_container);
419 inner_container->SetBounds(100, 10, 150, 180);
421 ScrollView* scroll_view = new ScrollView();
422 scroll_view->set_id(kScrollViewID);
423 inner_container->AddChildView(scroll_view);
424 scroll_view->SetBounds(1, 1, 148, 178);
426 View* scroll_content = new View();
427 scroll_content->SetBounds(0, 0, 200, 200);
428 scroll_content->set_background(
429 Background::CreateSolidBackground(200, 200, 200));
430 scroll_view->SetContents(scroll_content);
432 static const char* const kTitles[] = {
433 "Rosetta", "Stupeur et tremblement", "The diner game",
434 "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
435 "Joyeux Noel", "Camping", "Brice de Nice",
439 static const int kIDs[] = {
440 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
441 kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
442 kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
443 kTaxiLinkID, kAsterixLinkID
446 DCHECK(arraysize(kTitles) == arraysize(kIDs));
449 for (size_t i = 0; i < arraysize(kTitles); ++i) {
450 Link* link = new Link(ASCIIToUTF16(kTitles[i]));
451 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
452 link->set_id(kIDs[i]);
453 scroll_content->AddChildView(link);
454 link->SetBounds(5, y, 300, 15);
460 button = new LabelButton(NULL, ASCIIToUTF16("OK"));
461 button->SetStyle(Button::STYLE_BUTTON);
462 button->set_id(kOKButtonID);
463 button->SetIsDefault(true);
465 GetContentsView()->AddChildView(button);
466 button->SetBounds(150, y, width, 30);
468 button = new LabelButton(NULL, ASCIIToUTF16("Cancel"));
469 button->SetStyle(Button::STYLE_BUTTON);
470 button->set_id(kCancelButtonID);
471 GetContentsView()->AddChildView(button);
472 button->SetBounds(220, y, width, 30);
474 button = new LabelButton(NULL, ASCIIToUTF16("Help"));
475 button->SetStyle(Button::STYLE_BUTTON);
476 button->set_id(kHelpButtonID);
477 GetContentsView()->AddChildView(button);
478 button->SetBounds(290, y, width, 30);
482 View* contents = NULL;
485 // Left bottom box with style checkboxes.
486 contents = new View();
487 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
488 cb = new Checkbox(ASCIIToUTF16("Bold"));
489 contents->AddChildView(cb);
490 cb->SetBounds(10, 10, 50, 20);
491 cb->set_id(kBoldCheckBoxID);
493 cb = new Checkbox(ASCIIToUTF16("Italic"));
494 contents->AddChildView(cb);
495 cb->SetBounds(70, 10, 50, 20);
496 cb->set_id(kItalicCheckBoxID);
498 cb = new Checkbox(ASCIIToUTF16("Underlined"));
499 contents->AddChildView(cb);
500 cb->SetBounds(130, 10, 70, 20);
501 cb->set_id(kUnderlinedCheckBoxID);
503 link = new Link(ASCIIToUTF16("Help"));
504 contents->AddChildView(link);
505 link->SetBounds(10, 35, 70, 10);
506 link->set_id(kStyleHelpLinkID);
508 text_field = new Textfield();
509 contents->AddChildView(text_field);
510 text_field->SetBounds(10, 50, 100, 20);
511 text_field->set_id(kStyleTextEditID);
513 style_tab_ = new TabbedPane();
514 style_tab_->set_id(kStyleContainerID);
515 GetContentsView()->AddChildView(style_tab_);
516 style_tab_->SetBounds(10, y, 210, 100);
517 style_tab_->AddTab(ASCIIToUTF16("Style"), contents);
518 style_tab_->AddTab(ASCIIToUTF16("Other"), new View());
520 // Right bottom box with search.
521 contents = new View();
522 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
523 text_field = new Textfield();
524 contents->AddChildView(text_field);
525 text_field->SetBounds(10, 10, 100, 20);
526 text_field->set_id(kSearchTextfieldID);
528 button = new LabelButton(NULL, ASCIIToUTF16("Search"));
529 button->SetStyle(Button::STYLE_BUTTON);
530 contents->AddChildView(button);
531 button->SetBounds(112, 5, 60, 30);
532 button->set_id(kSearchButtonID);
534 link = new Link(ASCIIToUTF16("Help"));
535 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
536 link->set_id(kHelpLinkID);
537 contents->AddChildView(link);
538 link->SetBounds(175, 10, 30, 20);
540 search_border_view_ = new BorderView(contents);
541 search_border_view_->set_id(kSearchContainerID);
543 GetContentsView()->AddChildView(search_border_view_);
544 search_border_view_->SetBounds(300, y, 240, 50);
548 contents = new View();
549 contents->SetFocusable(true);
550 contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
551 contents->set_id(kThumbnailContainerID);
552 button = new LabelButton(NULL, ASCIIToUTF16("Star"));
553 button->SetStyle(Button::STYLE_BUTTON);
554 contents->AddChildView(button);
555 button->SetBounds(5, 5, 50, 30);
556 button->set_id(kThumbnailStarID);
557 button = new LabelButton(NULL, ASCIIToUTF16("SuperStar"));
558 button->SetStyle(Button::STYLE_BUTTON);
559 contents->AddChildView(button);
560 button->SetBounds(60, 5, 100, 30);
561 button->set_id(kThumbnailSuperStarID);
563 GetContentsView()->AddChildView(contents);
564 contents->SetBounds(250, y, 200, 50);
565 // We can only call RadioButton::SetChecked() on the radio-button is part of
566 // the view hierarchy.
567 radio_button_to_check->SetChecked(true);
570 TEST_F(FocusTraversalTest, NormalTraversal) {
571 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
572 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
573 kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID,
574 kRosettaLinkID, kStupeurEtTremblementLinkID,
575 kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
576 kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
577 kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
578 kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
579 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
580 kSearchTextfieldID, kSearchButtonID, kHelpLinkID,
581 kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
583 // Let's traverse the whole focus hierarchy (several times, to make sure it
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);
592 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
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);
604 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
609 TEST_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 };
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 };
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);
633 // Let's do one traversal (several times, to make sure it loops ok).
634 GetFocusManager()->ClearFocus();
635 for (int i = 0; i < 3; ++i) {
636 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
637 GetFocusManager()->AdvanceFocus(false);
638 focused_view = GetFocusManager()->GetFocusedView();
639 EXPECT_TRUE(focused_view != NULL);
641 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
645 // Same thing in reverse.
646 GetFocusManager()->ClearFocus();
647 for (int i = 0; i < 3; ++i) {
648 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
649 GetFocusManager()->AdvanceFocus(true);
650 focused_view = GetFocusManager()->GetFocusedView();
651 EXPECT_TRUE(focused_view != NULL);
653 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
658 TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) {
659 const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID,
660 kThumbnailContainerID };
662 const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID,
663 kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID,
664 kComboboxID, kBroccoliButtonID, kRosettaLinkID,
665 kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID,
666 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
667 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID,
668 kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID,
669 kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID,
670 kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID };
673 // Let's make some views invisible.
674 for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) {
675 View* v = FindViewByID(kInvisibleIDs[i]);
676 ASSERT_TRUE(v != NULL);
677 v->SetVisible(false);
681 // Let's do one traversal (several times, to make sure it loops ok).
682 GetFocusManager()->ClearFocus();
683 for (int i = 0; i < 3; ++i) {
684 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
685 GetFocusManager()->AdvanceFocus(false);
686 focused_view = GetFocusManager()->GetFocusedView();
687 EXPECT_TRUE(focused_view != NULL);
689 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
693 // Same thing in reverse.
694 GetFocusManager()->ClearFocus();
695 for (int i = 0; i < 3; ++i) {
696 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
697 GetFocusManager()->AdvanceFocus(true);
698 focused_view = GetFocusManager()->GetFocusedView();
699 EXPECT_TRUE(focused_view != NULL);
701 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
706 TEST_F(FocusTraversalTest, PaneTraversal) {
707 // Tests trapping the traversal within a pane - useful for full
708 // keyboard accessibility for toolbars.
710 // First test the left container.
711 const int kLeftTraversalIDs[] = {
713 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
714 kFruitButtonID, kFruitCheckBoxID, kComboboxID };
716 FocusSearch focus_search_left(left_container_, true, false);
717 left_container_->EnablePaneFocus(&focus_search_left);
718 FindViewByID(kComboboxID)->RequestFocus();
720 // Traverse the focus hierarchy within the pane several times.
721 for (int i = 0; i < 3; ++i) {
722 for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) {
723 GetFocusManager()->AdvanceFocus(false);
724 View* focused_view = GetFocusManager()->GetFocusedView();
725 EXPECT_TRUE(focused_view != NULL);
727 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
731 // Traverse in reverse order.
732 FindViewByID(kAppleTextfieldID)->RequestFocus();
733 for (int i = 0; i < 3; ++i) {
734 for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) {
735 GetFocusManager()->AdvanceFocus(true);
736 View* focused_view = GetFocusManager()->GetFocusedView();
737 EXPECT_TRUE(focused_view != NULL);
739 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
743 // Now test the right container, but this time with accessibility mode.
744 // Make some links not focusable, but mark one of them as
745 // "accessibility focusable", so it should show up in the traversal.
746 const int kRightTraversalIDs[] = {
747 kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID,
748 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
749 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID };
751 FocusSearch focus_search_right(right_container_, true, true);
752 right_container_->EnablePaneFocus(&focus_search_right);
753 FindViewByID(kRosettaLinkID)->SetFocusable(false);
754 FindViewByID(kStupeurEtTremblementLinkID)->SetFocusable(false);
755 FindViewByID(kDinerGameLinkID)->SetAccessibilityFocusable(true);
756 FindViewByID(kDinerGameLinkID)->SetFocusable(false);
757 FindViewByID(kAsterixLinkID)->RequestFocus();
759 // Traverse the focus hierarchy within the pane several times.
760 for (int i = 0; i < 3; ++i) {
761 for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) {
762 GetFocusManager()->AdvanceFocus(false);
763 View* focused_view = GetFocusManager()->GetFocusedView();
764 EXPECT_TRUE(focused_view != NULL);
766 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
770 // Traverse in reverse order.
771 FindViewByID(kBroccoliButtonID)->RequestFocus();
772 for (int i = 0; i < 3; ++i) {
773 for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) {
774 GetFocusManager()->AdvanceFocus(true);
775 View* focused_view = GetFocusManager()->GetFocusedView();
776 EXPECT_TRUE(focused_view != NULL);
778 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());