root/chrome/browser/ui/views/accessibility/accessibility_event_router_views_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. NotifyAccessibilityEvent
  2. DeleteDelegate
  3. GetContentsView
  4. GetWidget
  5. GetWidget
  6. role_
  7. GetAccessibleState
  8. set_name
  9. SetUp
  10. TearDown
  11. CreateWindowWithContents
  12. EnableAccessibilityAndListenToFocusNotifications
  13. ClearCallback
  14. OnControlEvent
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. BuildMenu
  22. IsCommandIdChecked
  23. IsCommandIdEnabled
  24. IsCommandIdVisible
  25. GetAcceleratorForCommandId
  26. ExecuteCommand
  27. TEST_F

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string>

#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/accessibility/accessibility_extension_api.h"
#include "chrome/browser/accessibility/accessibility_extension_api_constants.h"
#include "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
#include "chrome/test/base/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/test/test_views_delegate.h"
#include "ui/views/widget/native_widget.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

#if defined(OS_WIN)
#include "ui/base/win/scoped_ole_initializer.h"
#endif

#if defined(USE_AURA)
#include "ui/aura/test/aura_test_helper.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/compositor/test/context_factories_for_test.h"
#endif

using base::ASCIIToUTF16;

class AccessibilityViewsDelegate : public views::TestViewsDelegate {
 public:
  AccessibilityViewsDelegate() {}
  virtual ~AccessibilityViewsDelegate() {}

  // Overridden from views::TestViewsDelegate:
  virtual void NotifyAccessibilityEvent(
      views::View* view, ui::AXEvent event_type) OVERRIDE {
    AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
        view, event_type);
  }

  DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
};

class AccessibilityWindowDelegate : public views::WidgetDelegate {
 public:
  explicit AccessibilityWindowDelegate(views::View* contents)
      : contents_(contents) { }

  // Overridden from views::WidgetDelegate:
  virtual void DeleteDelegate() OVERRIDE { delete this; }
  virtual views::View* GetContentsView() OVERRIDE { return contents_; }
  virtual const views::Widget* GetWidget() const OVERRIDE {
    return contents_->GetWidget();
  }
  virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }

 private:
  views::View* contents_;

  DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
};

class ViewWithNameAndRole : public views::View {
 public:
  explicit ViewWithNameAndRole(const base::string16& name,
                               ui::AXRole role)
      : name_(name),
        role_(role) {
  }

  virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
    views::View::GetAccessibleState(state);
    state->name = name_;
    state->role = role_;
  }

  void set_name(const base::string16& name) { name_ = name; }

 private:
  base::string16 name_;
  ui::AXRole role_;
  DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
};

class AccessibilityEventRouterViewsTest
    : public testing::Test {
 public:
  AccessibilityEventRouterViewsTest() : control_event_count_(0) {
  }

  virtual void SetUp() {
#if defined(OS_WIN)
    ole_initializer_.reset(new ui::ScopedOleInitializer());
#endif
    views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
#if defined(USE_AURA)
    // The ContextFactory must exist before any Compositors are created.
    bool enable_pixel_output = false;
    ui::InitializeContextFactoryForTests(enable_pixel_output);

    aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
    aura_test_helper_->SetUp();
#endif  // USE_AURA
    EnableAccessibilityAndListenToFocusNotifications();
  }

  virtual void TearDown() {
    ClearCallback();
#if defined(USE_AURA)
    aura_test_helper_->TearDown();
    ui::TerminateContextFactoryForTests();
#endif
    delete views::ViewsDelegate::views_delegate;
    views::ViewsDelegate::views_delegate = NULL;

    // The Widget's FocusManager is deleted using DeleteSoon - this
    // forces it to be deleted now, so we don't have any memory leaks
    // when this method exits.
    base::MessageLoop::current()->RunUntilIdle();

#if defined(OS_WIN)
    ole_initializer_.reset();
#endif
  }

  views::Widget* CreateWindowWithContents(views::View* contents) {
    gfx::NativeView context = NULL;
#if defined(USE_AURA)
    context = aura_test_helper_->root_window();
#endif
    views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
        new AccessibilityWindowDelegate(contents),
        context,
        gfx::Rect(0, 0, 500, 500));

    // Create a profile and associate it with this window.
    widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);

    return widget;
  }

  void EnableAccessibilityAndListenToFocusNotifications() {
    // Switch on accessibility event notifications.
    ExtensionAccessibilityEventRouter* accessibility_event_router =
        ExtensionAccessibilityEventRouter::GetInstance();
    accessibility_event_router->SetAccessibilityEnabled(true);
    accessibility_event_router->SetControlEventCallbackForTesting(base::Bind(
        &AccessibilityEventRouterViewsTest::OnControlEvent,
        base::Unretained(this)));
  }

  void ClearCallback() {
    ExtensionAccessibilityEventRouter* accessibility_event_router =
        ExtensionAccessibilityEventRouter::GetInstance();
    accessibility_event_router->ClearControlEventCallback();
  }

 protected:
  // Handle Focus event.
  virtual void OnControlEvent(ui::AXEvent event,
                            const AccessibilityControlInfo* info) {
    control_event_count_++;
    last_control_type_ = info->type();
    last_control_name_ = info->name();
    last_control_context_ = info->context();
  }

  base::MessageLoopForUI message_loop_;
  int control_event_count_;
  std::string last_control_type_;
  std::string last_control_name_;
  std::string last_control_context_;
  TestingProfile profile_;
#if defined(OS_WIN)
  scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
#endif
#if defined(USE_AURA)
  scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
#endif
};

TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
  const char kButton1ASCII[] = "Button1";
  const char kButton2ASCII[] = "Button2";
  const char kButton3ASCII[] = "Button3";
  const char kButton3NewASCII[] = "Button3New";

  // Create a contents view with 3 buttons.
  views::View* contents = new views::View();
  views::LabelButton* button1 = new views::LabelButton(
      NULL, ASCIIToUTF16(kButton1ASCII));
  button1->SetStyle(views::Button::STYLE_BUTTON);
  contents->AddChildView(button1);
  views::LabelButton* button2 = new views::LabelButton(
      NULL, ASCIIToUTF16(kButton2ASCII));
  button2->SetStyle(views::Button::STYLE_BUTTON);
  contents->AddChildView(button2);
  views::LabelButton* button3 = new views::LabelButton(
      NULL, ASCIIToUTF16(kButton3ASCII));
  button3->SetStyle(views::Button::STYLE_BUTTON);
  contents->AddChildView(button3);

  // Put the view in a window.
  views::Widget* window = CreateWindowWithContents(contents);
  window->Show();

  // Set focus to the first button initially and run message loop to execute
  // callback.
  button1->RequestFocus();
  base::MessageLoop::current()->RunUntilIdle();

  // Change the accessible name of button3.
  button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));

  // Advance focus to the next button and test that we got the
  // expected notification with the name of button 2.
  views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager();
  control_event_count_ = 0;
  focus_manager->AdvanceFocus(false);
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(1, control_event_count_);
  EXPECT_EQ(kButton2ASCII, last_control_name_);

  // Advance to button 3. Expect the new accessible name we assigned.
  focus_manager->AdvanceFocus(false);
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(2, control_event_count_);
  EXPECT_EQ(kButton3NewASCII, last_control_name_);

  // Advance to button 1 and check the notification.
  focus_manager->AdvanceFocus(false);
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(3, control_event_count_);
  EXPECT_EQ(kButton1ASCII, last_control_name_);

  window->CloseNow();
}

TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
  const char kToolbarNameASCII[] = "MyToolbar";
  const char kButtonNameASCII[] = "MyButton";

  // Create a toolbar with a button.
  views::View* contents = new ViewWithNameAndRole(
      ASCIIToUTF16(kToolbarNameASCII),
      ui::AX_ROLE_TOOLBAR);
  views::LabelButton* button = new views::LabelButton(
      NULL, ASCIIToUTF16(kButtonNameASCII));
  button->SetStyle(views::Button::STYLE_BUTTON);
  contents->AddChildView(button);

  // Put the view in a window.
  views::Widget* window = CreateWindowWithContents(contents);

  // Set focus to the button.
  control_event_count_ = 0;
  button->RequestFocus();

  base::MessageLoop::current()->RunUntilIdle();

  // Test that we got the event with the expected name and context.
  EXPECT_EQ(1, control_event_count_);
  EXPECT_EQ(kButtonNameASCII, last_control_name_);
  EXPECT_EQ(kToolbarNameASCII, last_control_context_);

  window->CloseNow();
}

TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
  const char kAlertTextASCII[] = "MyAlertText";
  const char kButtonNameASCII[] = "MyButton";

  // Create an alert with static text and a button, similar to an infobar.
  views::View* contents = new ViewWithNameAndRole(
      base::string16(),
      ui::AX_ROLE_ALERT);
  views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII));
  contents->AddChildView(label);
  views::LabelButton* button = new views::LabelButton(
      NULL, ASCIIToUTF16(kButtonNameASCII));
  button->SetStyle(views::Button::STYLE_BUTTON);
  contents->AddChildView(button);

  // Put the view in a window.
  views::Widget* window = CreateWindowWithContents(contents);

  // Set focus to the button.
  control_event_count_ = 0;
  button->RequestFocus();

  base::MessageLoop::current()->RunUntilIdle();

  // Test that we got the event with the expected name and context.
  EXPECT_EQ(1, control_event_count_);
  EXPECT_EQ(kButtonNameASCII, last_control_name_);
  EXPECT_EQ(kAlertTextASCII, last_control_context_);

  window->CloseNow();
}

TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
  const char kContentsNameASCII[] = "Contents";
  const char kOldNameASCII[] = "OldName";
  const char kNewNameASCII[] = "NewName";

  // Create a toolbar with a button.
  views::View* contents = new ViewWithNameAndRole(
      ASCIIToUTF16(kContentsNameASCII),
      ui::AX_ROLE_CLIENT);
  ViewWithNameAndRole* child = new ViewWithNameAndRole(
      ASCIIToUTF16(kOldNameASCII),
      ui::AX_ROLE_BUTTON);
  child->SetFocusable(true);
  contents->AddChildView(child);

  // Put the view in a window.
  views::Widget* window = CreateWindowWithContents(contents);

  // Set focus to the child view.
  control_event_count_ = 0;
  child->RequestFocus();

  // Change the child's name after the focus notification.
  child->set_name(ASCIIToUTF16(kNewNameASCII));

  // We shouldn't get the notification right away.
  EXPECT_EQ(0, control_event_count_);

  // Process anything in the event loop. Now we should get the notification,
  // and it should give us the new control name, not the old one.
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(1, control_event_count_);
  EXPECT_EQ(kNewNameASCII, last_control_name_);

  window->CloseNow();
}

TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
  const char kContentsNameASCII[] = "Contents";
  const char kNameASCII[] = "OldName";

  // Create a toolbar with a button.
  views::View* contents = new ViewWithNameAndRole(
      ASCIIToUTF16(kContentsNameASCII),
      ui::AX_ROLE_CLIENT);
  ViewWithNameAndRole* child = new ViewWithNameAndRole(
      ASCIIToUTF16(kNameASCII),
      ui::AX_ROLE_BUTTON);
  child->SetFocusable(true);
  contents->AddChildView(child);

  // Put the view in a window.
  views::Widget* window = CreateWindowWithContents(contents);

  // Set focus to the child view.
  control_event_count_ = 0;
  child->RequestFocus();

  // Delete the child!
  delete child;

  // We shouldn't get the notification right away.
  EXPECT_EQ(0, control_event_count_);

  // Process anything in the event loop. We shouldn't get a notification
  // because the view is no longer valid, and this shouldn't crash.
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(0, control_event_count_);

  window->CloseNow();
}

TEST_F(AccessibilityEventRouterViewsTest, AlertsFromWindowAndControl) {
  const char kButtonASCII[] = "Button";
  const char* kTypeAlert = extension_accessibility_api_constants::kTypeAlert;
  const char* kTypeWindow = extension_accessibility_api_constants::kTypeWindow;

  // Create a contents view with a button.
  views::View* contents = new views::View();
  views::LabelButton* button = new views::LabelButton(
      NULL, ASCIIToUTF16(kButtonASCII));
  button->SetStyle(views::Button::STYLE_BUTTON);
  contents->AddChildView(button);

  // Put the view in a window.
  views::Widget* window = CreateWindowWithContents(contents);
  window->Show();

  // Send an alert event from the button and let the event loop run.
  control_event_count_ = 0;
  button->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
  base::MessageLoop::current()->RunUntilIdle();

  EXPECT_EQ(kTypeAlert, last_control_type_);
  EXPECT_EQ(1, control_event_count_);
  EXPECT_EQ(kButtonASCII, last_control_name_);

  // Send an alert event from the window and let the event loop run.
  control_event_count_ = 0;
  window->GetRootView()->NotifyAccessibilityEvent(
      ui::AX_EVENT_ALERT, true);
  base::MessageLoop::current()->RunUntilIdle();

  EXPECT_EQ(1, control_event_count_);
  EXPECT_EQ(kTypeWindow, last_control_type_);

  window->CloseNow();
}

namespace {

class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
 public:
  enum {
    IDC_MENU_ITEM_1,
    IDC_MENU_ITEM_2,
    IDC_MENU_INVISIBLE,
    IDC_MENU_ITEM_3,
  };

  SimpleMenuDelegate() {}
  virtual ~SimpleMenuDelegate() {}

  views::MenuItemView* BuildMenu() {
    menu_model_.reset(new ui::SimpleMenuModel(this));
    menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1"));
    menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2"));
    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
    menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible"));
    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
    menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3"));

    menu_runner_.reset(new views::MenuRunner(menu_model_.get()));
    return menu_runner_->GetMenu();
  }

  virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
    return false;
  }

  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
    return true;
  }

  virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
    return command_id != IDC_MENU_INVISIBLE;
  }

  virtual bool GetAcceleratorForCommandId(
      int command_id,
      ui::Accelerator* accelerator) OVERRIDE {
    return false;
  }

  virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
  }

 private:
  scoped_ptr<ui::SimpleMenuModel> menu_model_;
  scoped_ptr<views::MenuRunner> menu_runner_;

  DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
};

}  // namespace

TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
  SimpleMenuDelegate menu_delegate;
  views::MenuItemView* menu = menu_delegate.BuildMenu();
  views::View* menu_container = menu->CreateSubmenu();

  struct TestCase {
    int command_id;
    int expected_index;
    int expected_count;
  } kTestCases[] = {
    { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
    { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
    { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
    { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
    int index = 0;
    int count = 0;

    AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
        menu_container,
        menu->GetMenuItemByID(kTestCases[i].command_id),
        &index,
        &count);
    EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
    EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;
  }
}

/* [<][>][^][v][top][bottom][index][help] */