Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / controls / menu / menu_runner_cocoa_unittest.mm
blob366dd17ee7699f774c8c3856b5309925b31a2b80
1 // Copyright 2014 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 #import "ui/views/controls/menu/menu_runner_impl_cocoa.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/strings/utf_string_conversions.h"
10 #import "testing/gtest_mac.h"
11 #include "ui/base/models/simple_menu_model.h"
12 #include "ui/events/event_utils.h"
13 #include "ui/views/test/views_test_base.h"
15 namespace views {
16 namespace test {
17 namespace {
19 class TestModel : public ui::SimpleMenuModel {
20  public:
21   TestModel() : ui::SimpleMenuModel(&delegate_), delegate_(this) {}
23   void set_checked_command(int command) { checked_command_ = command; }
25  private:
26   class Delegate : public ui::SimpleMenuModel::Delegate {
27    public:
28     explicit Delegate(TestModel* model) : model_(model) {}
29     bool IsCommandIdChecked(int command_id) const override {
30       return command_id == model_->checked_command_;
31     }
32     bool IsCommandIdEnabled(int command_id) const override { return true; }
33     bool GetAcceleratorForCommandId(int command_id,
34                                     ui::Accelerator* accelerator) override {
35       return false;
36     }
37     void ExecuteCommand(int command_id, int event_flags) override {}
39    private:
40     TestModel* model_;
42     DISALLOW_COPY_AND_ASSIGN(Delegate);
43   };
45  private:
46   int checked_command_ = -1;
47   Delegate delegate_;
49   DISALLOW_COPY_AND_ASSIGN(TestModel);
52 }  // namespace
54 class MenuRunnerCocoaTest : public ViewsTestBase {
55  public:
56   MenuRunnerCocoaTest() {}
57   ~MenuRunnerCocoaTest() override {}
59   void SetUp() override {
60     ViewsTestBase::SetUp();
62     menu_.reset(new TestModel());
63     menu_->AddCheckItem(0, base::ASCIIToUTF16("Menu Item"));
65     runner_ = new internal::MenuRunnerImplCocoa(menu_.get());
66     EXPECT_FALSE(runner_->IsRunning());
67   }
69   void TearDown() override {
70     if (runner_) {
71       runner_->Release();
72       runner_ = NULL;
73     }
75     ViewsTestBase::TearDown();
76   }
78   // Runs the menu after scheduling |block| on the run loop.
79   MenuRunner::RunResult RunMenu(dispatch_block_t block) {
80     CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
81         EXPECT_TRUE(runner_->IsRunning());
82         block();
83     });
84     return runner_->RunMenuAt(
85         NULL, NULL, gfx::Rect(), MENU_ANCHOR_TOPLEFT, MenuRunner::CONTEXT_MENU);
86   }
88   // Runs then cancels a combobox menu and captures the frame of the anchoring
89   // view.
90   MenuRunner::RunResult RunMenuAt(const gfx::Rect& anchor) {
91     last_anchor_frame_ = NSZeroRect;
93     // Should be one child (the compositor layer) before showing, and it should
94     // go up by one (the anchor view) while the menu is shown.
95     EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]);
96     CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
97       NSArray* subviews = [parent_->GetNativeView() subviews];
98       EXPECT_EQ(2u, [subviews count]);
99       last_anchor_frame_ = [[subviews objectAtIndex:1] frame];
100       runner_->Cancel();
101     });
102     MenuRunner::RunResult result = runner_->RunMenuAt(
103         parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT, MenuRunner::COMBOBOX);
105     // Ensure the anchor view is removed.
106     EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]);
107     return result;
108   }
110  protected:
111   scoped_ptr<TestModel> menu_;
112   internal::MenuRunnerImplCocoa* runner_ = nullptr;
113   views::Widget* parent_ = nullptr;
114   NSRect last_anchor_frame_ = NSZeroRect;
116  private:
117   DISALLOW_COPY_AND_ASSIGN(MenuRunnerCocoaTest);
120 TEST_F(MenuRunnerCocoaTest, RunMenuAndCancel) {
121   base::TimeDelta min_time = ui::EventTimeForNow();
123   MenuRunner::RunResult result = RunMenu(^{
124       runner_->Cancel();
125       EXPECT_FALSE(runner_->IsRunning());
126   });
128   EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
129   EXPECT_FALSE(runner_->IsRunning());
131   EXPECT_GE(runner_->GetClosingEventTime(), min_time);
132   EXPECT_LE(runner_->GetClosingEventTime(), ui::EventTimeForNow());
134   // Cancel again.
135   runner_->Cancel();
136   EXPECT_FALSE(runner_->IsRunning());
139 TEST_F(MenuRunnerCocoaTest, RunMenuAndDelete) {
140   MenuRunner::RunResult result = RunMenu(^{
141       runner_->Release();
142       runner_ = NULL;
143   });
145   EXPECT_EQ(MenuRunner::MENU_DELETED, result);
148 TEST_F(MenuRunnerCocoaTest, RunMenuTwice) {
149   for (int i = 0; i < 2; ++i) {
150     MenuRunner::RunResult result = RunMenu(^{
151         runner_->Cancel();
152     });
153     EXPECT_EQ(MenuRunner::NORMAL_EXIT, result);
154     EXPECT_FALSE(runner_->IsRunning());
155   }
158 TEST_F(MenuRunnerCocoaTest, CancelWithoutRunning) {
159   runner_->Cancel();
160   EXPECT_FALSE(runner_->IsRunning());
161   EXPECT_EQ(base::TimeDelta(), runner_->GetClosingEventTime());
164 TEST_F(MenuRunnerCocoaTest, DeleteWithoutRunning) {
165   runner_->Release();
166   runner_ = NULL;
169 // Tests anchoring of the menus used for toolkit-views Comboboxes.
170 TEST_F(MenuRunnerCocoaTest, ComboboxAnchoring) {
171   const int kWindowHeight = 200;
172   const int kWindowOffset = 100;
174   parent_ = new views::Widget();
175   parent_->Init(CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS));
176   parent_->SetBounds(
177       gfx::Rect(kWindowOffset, kWindowOffset, 300, kWindowHeight));
178   parent_->Show();
180   // Combobox at 20,10 in the Widget.
181   const gfx::Rect combobox_rect(20, 10, 80, 50);
183   // Menu anchor rects are always in screen coordinates. The window is frameless
184   // so offset by the bounds.
185   gfx::Rect anchor_rect = combobox_rect;
186   anchor_rect.Offset(kWindowOffset, kWindowOffset);
187   RunMenuAt(anchor_rect);
189   // Nothing is checked, so the anchor view should have no height, to ensure the
190   // menu goes below the anchor rect. There should also be no x-offset since the
191   // there is no need to line-up text.
192   EXPECT_NSEQ(
193       NSMakeRect(combobox_rect.x(), kWindowHeight - combobox_rect.bottom(),
194                  combobox_rect.width(), 0),
195       last_anchor_frame_);
197   menu_->set_checked_command(0);
198   RunMenuAt(anchor_rect);
200   // Native constant used by MenuRunnerImplCocoa.
201   const CGFloat kNativeCheckmarkWidth = 18;
203   // There is now a checked item, so the anchor should be vertically centered
204   // inside the combobox, and offset by the width of the checkmark column.
205   EXPECT_EQ(combobox_rect.x() - kNativeCheckmarkWidth,
206             last_anchor_frame_.origin.x);
207   EXPECT_EQ(kWindowHeight - combobox_rect.CenterPoint().y(),
208             NSMidY(last_anchor_frame_));
209   EXPECT_EQ(combobox_rect.width(), NSWidth(last_anchor_frame_));
210   EXPECT_NE(0, NSHeight(last_anchor_frame_));
212   // In RTL, Cocoa messes up the positioning unless the anchor rectangle is
213   // offset to the right of the view. The offset for the checkmark is also
214   // skipped, to give a better match to native behavior.
215   base::i18n::SetICUDefaultLocale("he");
216   RunMenuAt(anchor_rect);
217   EXPECT_EQ(combobox_rect.right(), last_anchor_frame_.origin.x);
219   parent_->CloseNow();
222 }  // namespace test
223 }  // namespace views