blob: 7e524993e485387c84bf10fbf3c92b802c3b34f4 [file] [log] [blame]
// 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 "ui/views/controls/menu/menu_model_adapter.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/base/view_event_test_base.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/test/ui_controls.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/menu_button_listener.h"
#include "ui/views/controls/menu/menu_controller.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/test/menu_test_utils.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
namespace {
const int kTopMenuBaseId = 100;
const int kSubMenuBaseId = 200;
// Implement most of the ui::MenuModel pure virtual methods for subclasses
//
// Exceptions:
// virtual int GetItemCount() const = 0;
// virtual ItemType GetTypeAt(int index) const = 0;
// virtual int GetCommandIdAt(int index) const = 0;
// virtual base::string16 GetLabelAt(int index) const = 0;
class CommonMenuModel : public ui::MenuModel {
public:
CommonMenuModel() {
}
~CommonMenuModel() override {}
protected:
// ui::MenuModel implementation.
bool HasIcons() const override { return false; }
bool IsItemDynamicAt(int index) const override { return false; }
bool GetAcceleratorAt(int index,
ui::Accelerator* accelerator) const override {
return false;
}
ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override {
return ui::NORMAL_SEPARATOR;
}
bool IsItemCheckedAt(int index) const override { return false; }
int GetGroupIdAt(int index) const override { return 0; }
bool GetIconAt(int index, gfx::Image* icon) override { return false; }
ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
return nullptr;
}
bool IsEnabledAt(int index) const override { return true; }
ui::MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; }
void HighlightChangedTo(int index) override {}
void ActivatedAt(int index) override {}
void SetMenuModelDelegate(ui::MenuModelDelegate* delegate) override {}
ui::MenuModelDelegate* GetMenuModelDelegate() const override {
return nullptr;
}
private:
DISALLOW_COPY_AND_ASSIGN(CommonMenuModel);
};
class SubMenuModel : public CommonMenuModel {
public:
SubMenuModel()
: showing_(false) {
}
~SubMenuModel() override {}
bool showing() const {
return showing_;
}
private:
// ui::MenuModel implementation.
int GetItemCount() const override { return 1; }
ItemType GetTypeAt(int index) const override { return TYPE_COMMAND; }
int GetCommandIdAt(int index) const override {
return index + kSubMenuBaseId;
}
base::string16 GetLabelAt(int index) const override {
return base::ASCIIToUTF16("Item");
}
void MenuWillShow() override { showing_ = true; }
// Called when the menu is about to close.
void MenuWillClose() override { showing_ = false; }
bool showing_;
DISALLOW_COPY_AND_ASSIGN(SubMenuModel);
};
class TopMenuModel : public CommonMenuModel {
public:
TopMenuModel() {
}
~TopMenuModel() override {}
bool IsSubmenuShowing() {
return sub_menu_model_.showing();
}
private:
// ui::MenuModel implementation.
int GetItemCount() const override { return 1; }
ItemType GetTypeAt(int index) const override { return TYPE_SUBMENU; }
int GetCommandIdAt(int index) const override {
return index + kTopMenuBaseId;
}
base::string16 GetLabelAt(int index) const override {
return base::ASCIIToUTF16("submenu");
}
MenuModel* GetSubmenuModelAt(int index) const override {
return &sub_menu_model_;
}
mutable SubMenuModel sub_menu_model_;
DISALLOW_COPY_AND_ASSIGN(TopMenuModel);
};
} // namespace
class MenuModelAdapterTest : public ViewEventTestBase,
public views::MenuButtonListener {
public:
MenuModelAdapterTest()
: ViewEventTestBase(),
button_(nullptr),
menu_model_adapter_(&top_menu_model_),
menu_(nullptr) {}
~MenuModelAdapterTest() override {}
// ViewEventTestBase implementation.
void SetUp() override {
button_ =
new views::MenuButton(base::ASCIIToUTF16("Menu Adapter Test"), this);
menu_ = menu_model_adapter_.CreateMenu();
menu_runner_.reset(
new views::MenuRunner(menu_, views::MenuRunner::HAS_MNEMONICS));
ViewEventTestBase::SetUp();
}
void TearDown() override {
menu_runner_ = nullptr;
menu_ = nullptr;
ViewEventTestBase::TearDown();
}
views::View* CreateContentsView() override { return button_; }
gfx::Size GetPreferredSizeForContents() const override {
return button_->GetPreferredSize();
}
// views::MenuButtonListener implementation.
void OnMenuButtonClicked(views::MenuButton* source,
const gfx::Point& point,
const ui::Event* event) override {
gfx::Point screen_location;
views::View::ConvertPointToScreen(source, &screen_location);
gfx::Rect bounds(screen_location, source->size());
menu_runner_->RunMenuAt(source->GetWidget(), button_, bounds,
views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE);
}
// ViewEventTestBase implementation
void DoTestOnMessageLoop() override {
Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step1));
}
// Open the submenu.
void Step1() {
views::test::DisableMenuClosureAnimations();
views::SubmenuView* topmenu = menu_->GetSubmenu();
ASSERT_TRUE(topmenu);
ASSERT_TRUE(topmenu->IsShowing());
ASSERT_FALSE(top_menu_model_.IsSubmenuShowing());
// Click the first item to open the submenu.
views::MenuItemView* item = topmenu->GetMenuItemAt(0);
ASSERT_TRUE(item);
Click(item, CreateEventTask(this, &MenuModelAdapterTest::Step2));
}
// Rebuild the menu which should close the submenu.
void Step2() {
views::SubmenuView* topmenu = menu_->GetSubmenu();
ASSERT_TRUE(topmenu);
ASSERT_TRUE(topmenu->IsShowing());
ASSERT_TRUE(top_menu_model_.IsSubmenuShowing());
menu_model_adapter_.BuildMenu(menu_);
ASSERT_TRUE(base::MessageLoopCurrentForUI::IsSet());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, CreateEventTask(this, &MenuModelAdapterTest::Step3));
}
// Verify that the submenu MenuModel received the close callback
// and close the menu.
void Step3() {
views::SubmenuView* topmenu = menu_->GetSubmenu();
ASSERT_TRUE(topmenu);
ASSERT_TRUE(topmenu->IsShowing());
ASSERT_FALSE(top_menu_model_.IsSubmenuShowing());
// Click the button to exit the menu.
Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step4));
}
// All done.
void Step4() {
views::SubmenuView* topmenu = menu_->GetSubmenu();
views::test::WaitForMenuClosureAnimation();
ASSERT_TRUE(topmenu);
ASSERT_FALSE(topmenu->IsShowing());
ASSERT_FALSE(top_menu_model_.IsSubmenuShowing());
Done();
}
private:
// Generate a mouse click on the specified view and post a new task.
virtual void Click(views::View* view, const base::Closure& next) {
ui_test_utils::MoveMouseToCenterAndPress(
view,
ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP,
next);
}
views::MenuButton* button_;
TopMenuModel top_menu_model_;
views::MenuModelAdapter menu_model_adapter_;
views::MenuItemView* menu_;
std::unique_ptr<views::MenuRunner> menu_runner_;
};
// If this flakes, disable and log details in http://crbug.com/523255.
VIEW_TEST(MenuModelAdapterTest, RebuildMenu)