File: toolbar_action_view_unittest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (368 lines) | stat: -rw-r--r-- 14,022 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"

#include <memory>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "content/public/test/test_web_contents_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/menu_button.h"

namespace {

// A test delegate for a toolbar action view.
class TestToolbarActionViewDelegate : public ToolbarActionView::Delegate {
 public:
  TestToolbarActionViewDelegate()
      : overflow_reference_view_(std::make_unique<views::MenuButton>()),
        web_contents_(nullptr) {}
  TestToolbarActionViewDelegate(const TestToolbarActionViewDelegate&) = delete;
  TestToolbarActionViewDelegate& operator=(
      const TestToolbarActionViewDelegate&) = delete;
  ~TestToolbarActionViewDelegate() override = default;

  // ToolbarActionView::Delegate:
  content::WebContents* GetCurrentWebContents() override {
    return web_contents_;
  }
  views::MenuButton* GetOverflowReferenceView() const override {
    return overflow_reference_view_.get();
  }
  gfx::Size GetToolbarActionSize() override { return gfx::Size(32, 32); }
  MOCK_METHOD(void,
              MovePinnedActionBy,
              (const std::string& action_id, int move_by),
              (override));
  void WriteDragDataForView(views::View* sender,
                            const gfx::Point& press_pt,
                            ui::OSExchangeData* data) override {}
  int GetDragOperationsForView(views::View* sender,
                               const gfx::Point& p) override {
    return ui::DragDropTypes::DRAG_NONE;
  }
  bool CanStartDragForView(views::View* sender,
                           const gfx::Point& press_pt,
                           const gfx::Point& p) override {
    return false;
  }

  void set_web_contents(content::WebContents* web_contents) {
    web_contents_ = web_contents;
  }

 private:
  std::unique_ptr<views::MenuButton> overflow_reference_view_;

  raw_ptr<content::WebContents> web_contents_;
};

class OpenMenuListener : public views::ContextMenuController {
 public:
  explicit OpenMenuListener(views::View* view) : view_(view) {
    view_->set_context_menu_controller(this);
  }
  OpenMenuListener(const OpenMenuListener&) = delete;
  OpenMenuListener& operator=(const OpenMenuListener&) = delete;
  ~OpenMenuListener() override { view_->set_context_menu_controller(nullptr); }

  void ShowContextMenuForViewImpl(
      views::View* source,
      const gfx::Point& point,
      ui::mojom::MenuSourceType source_type) override {
    opened_menu_ = true;
  }

  bool opened_menu() const { return opened_menu_; }

 private:
  raw_ptr<views::View> view_;

  bool opened_menu_ = false;
};

}  // namespace

class ToolbarActionViewUnitTest : public ChromeViewsTestBase {
 public:
  ToolbarActionViewUnitTest() : widget_(nullptr) {}
  ToolbarActionViewUnitTest(const ToolbarActionViewUnitTest&) = delete;
  ToolbarActionViewUnitTest& operator=(const ToolbarActionViewUnitTest&) =
      delete;
  ~ToolbarActionViewUnitTest() override = default;

  void SetUp() override {
    ChromeViewsTestBase::SetUp();
    controller_ =
        std::make_unique<TestToolbarActionViewController>("fake controller");
    action_view_delegate_ =
        std::make_unique<testing::NiceMock<TestToolbarActionViewDelegate>>();
    widget_ =
        CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
  }

  void TearDown() override {
    widget_.reset();
    ChromeViewsTestBase::TearDown();
  }

  void Reset() {
    widget_.reset();
    action_view_delegate_.reset();
    controller_.reset();
  }

  views::Widget* widget() { return widget_.get(); }

  TestToolbarActionViewController* controller() { return controller_.get(); }

  TestToolbarActionViewDelegate* action_view_delegate() {
    return action_view_delegate_.get();
  }
  testing::NiceMock<TestToolbarActionViewDelegate>&
  mock_action_view_delegate() {
    return *action_view_delegate_;
  }

 private:
  std::unique_ptr<TestToolbarActionViewController> controller_;
  std::unique_ptr<testing::NiceMock<TestToolbarActionViewDelegate>>
      action_view_delegate_;

  // The widget managed by this test.
  std::unique_ptr<views::Widget> widget_;
};

// A MenuButton subclass that provides access to some MenuButton internals.
class TestToolbarActionView : public ToolbarActionView {
 public:
  TestToolbarActionView(ToolbarActionViewController* view_controller,
                        Delegate* delegate)
      : ToolbarActionView(view_controller, delegate) {}
  TestToolbarActionView(const TestToolbarActionView&) = delete;
  TestToolbarActionView& operator=(const TestToolbarActionView&) = delete;
  ~TestToolbarActionView() override = default;
};

// Verifies there is no crash when a ToolbarActionView with an InkDrop is
// destroyed while holding a |pressed_lock_|.
TEST_F(ToolbarActionViewUnitTest,
       NoCrashWhenDestroyingToolbarActionViewThatHasAPressedLock) {
  TestToolbarActionViewController* view_controller = controller();

  // Create a new toolbar action view.
  auto view = std::make_unique<ToolbarActionView>(view_controller,
                                                  action_view_delegate());
  view->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
  widget()->SetContentsView(std::move(view));
  widget()->Show();

  view_controller->ShowPopup(true);
}

// Verifies the InkDropAnimation used by the ToolbarActionView doesn't fail a
// DCHECK for an unsupported transition from ACTIVATED to ACTION_PENDING.
TEST_F(ToolbarActionViewUnitTest,
       NoCrashWhenPressingMouseOnToolbarActionViewThatHasAPressedLock) {
  TestToolbarActionViewController* view_controller = controller();

  // Create a new toolbar action view.
  auto view = std::make_unique<ToolbarActionView>(view_controller,
                                                  action_view_delegate());
  view->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
  widget()->SetContentsView(std::move(view));
  widget()->Show();

  ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());

  view_controller->ShowPopup(true);
  generator.PressLeftButton();
}

// Test the basic ui of a ToolbarActionView and that it responds correctly to
// a controller's state.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
    BUILDFLAG(IS_WIN)
// TODO(crbug.com/40668368): Test is flaky on Mac, Linux and Win10.
#define MAYBE_BasicToolbarActionViewTest DISABLED_BasicToolbarActionViewTest
#else
#define MAYBE_BasicToolbarActionViewTest BasicToolbarActionViewTest
#endif
TEST_F(ToolbarActionViewUnitTest, MAYBE_BasicToolbarActionViewTest) {
  TestingProfile profile;

  // ViewsTestBase initializes the aura environment, so the factory shouldn't.
  content::TestWebContentsFactory web_contents_factory;

  TestToolbarActionViewController* view_controller = controller();
  TestToolbarActionViewDelegate* view_delegate = action_view_delegate();

  // Configure the test controller and delegate.
  std::u16string name = u"name";
  view_controller->SetAccessibleName(name);
  std::u16string tooltip = u"tooltip";
  view_controller->SetTooltip(tooltip);
  content::WebContents* web_contents =
      web_contents_factory.CreateWebContents(&profile);
  CreateSessionServiceTabHelper(web_contents);
  view_delegate->set_web_contents(web_contents);

  // Move the mouse off the not-yet-existent button.
  ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
  generator.MoveMouseTo(gfx::Point(300, 300));

  // Create a new toolbar action view.
  auto owning_view =
      std::make_unique<ToolbarActionView>(view_controller, view_delegate);
  owning_view->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
  ToolbarActionView* view = owning_view.get();
  widget()->SetContentsView(std::move(owning_view));
  widget()->Show();

  // Check that the tooltip and accessible state of the view match the
  // controller's.
  EXPECT_EQ(tooltip, view->GetRenderedTooltipText(gfx::Point()));
  ui::AXNodeData ax_node_data;
  view->GetViewAccessibility().GetAccessibleNodeData(&ax_node_data);
  EXPECT_EQ(name, ax_node_data.GetString16Attribute(
                      ax::mojom::StringAttribute::kName));

  // The button should start in normal state, with no actions executed.
  EXPECT_EQ(views::Button::STATE_NORMAL, view->GetState());
  EXPECT_EQ(0, view_controller->execute_action_count());

  // Click the button. This should execute it.
  generator.MoveMouseTo(gfx::Point(10, 10));
  generator.ClickLeftButton();
  EXPECT_EQ(1, view_controller->execute_action_count());

  // Move the mouse off the button, and show a popup through a non-user action.
  // Since this was not a user action, the button should not be pressed.
  generator.MoveMouseTo(gfx::Point(300, 300));
  view_controller->ShowPopup(false);
  EXPECT_EQ(views::Button::STATE_NORMAL, view->GetState());
  view_controller->HidePopup();

  // Show the popup through a user action - the button should be pressed.
  view_controller->ShowPopup(true);
  EXPECT_EQ(views::Button::STATE_PRESSED, view->GetState());
  view_controller->HidePopup();
  EXPECT_EQ(views::Button::STATE_NORMAL, view->GetState());

  // Ensure that clicking on an otherwise-disabled action opens the
  // context menu.
  view_controller->SetEnabled(false);
  // Even though the controller is disabled, the button remains enabled
  // because it will open the context menu.
  EXPECT_EQ(views::Button::STATE_NORMAL, view->GetState());
  int old_execute_action_count = view_controller->execute_action_count();
  {
    OpenMenuListener menu_listener(view);
    view->Activate(nullptr);
    EXPECT_TRUE(menu_listener.opened_menu());
    EXPECT_EQ(old_execute_action_count,
              view_controller->execute_action_count());
  }

  // Create an overflow button.
  views::MenuButton* overflow_button =
      view_delegate->GetOverflowReferenceView();

  // If the view isn't visible, the overflow button should be pressed for
  // popups.
  view->SetVisible(false);
  view_controller->ShowPopup(true);
  EXPECT_EQ(views::Button::STATE_NORMAL, view->GetState());
  EXPECT_EQ(views::Button::STATE_PRESSED, overflow_button->GetState());
  view_controller->HidePopup();
  EXPECT_EQ(views::Button::STATE_NORMAL, view->GetState());
  EXPECT_EQ(views::Button::STATE_NORMAL, overflow_button->GetState());
}

// Verifies that pressing [Ctrl|Cmd] + Left / Right triggers a call to move the
// toolbar action.
TEST_F(ToolbarActionViewUnitTest, TestKeyboardReordering) {
  TestToolbarActionViewController* view_controller = controller();

  auto owned_view = std::make_unique<ToolbarActionView>(view_controller,
                                                        action_view_delegate());
  auto* view = owned_view.get();
  view->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
  widget()->SetContentsView(std::move(owned_view));
  widget()->Show();

  auto send_key_press = [view](ui::KeyboardCode code, int flags) {
    view->OnKeyPressed(ui::KeyEvent(ui::EventType::kKeyPressed, code, flags,
                                    ui::EventTimeForNow()));
    view->OnKeyReleased(ui::KeyEvent(ui::EventType::kKeyPressed, code, flags,
                                     ui::EventTimeForNow()));
  };

  // Start by pressing just left / right. No modifier is present, so this should
  // do nothing.
  EXPECT_CALL(mock_action_view_delegate(), MovePinnedActionBy).Times(0);
  send_key_press(ui::VKEY_RIGHT, ui::EF_NONE);
  testing::Mock::VerifyAndClearExpectations(&mock_action_view_delegate());

  EXPECT_CALL(mock_action_view_delegate(), MovePinnedActionBy).Times(0);
  send_key_press(ui::VKEY_LEFT, ui::EF_NONE);
  testing::Mock::VerifyAndClearExpectations(&mock_action_view_delegate());

  // Next, do the same with the wrong modifier key. Again, this should do
  // nothing.
  constexpr int kWrongModifierFlag =
#if BUILDFLAG(IS_MAC)
      ui::EF_CONTROL_DOWN;
#else
      ui::EF_COMMAND_DOWN;
#endif

  EXPECT_CALL(mock_action_view_delegate(), MovePinnedActionBy).Times(0);
  send_key_press(ui::VKEY_RIGHT, kWrongModifierFlag);
  testing::Mock::VerifyAndClearExpectations(&mock_action_view_delegate());

  EXPECT_CALL(mock_action_view_delegate(), MovePinnedActionBy).Times(0);
  send_key_press(ui::VKEY_LEFT, kWrongModifierFlag);
  testing::Mock::VerifyAndClearExpectations(&mock_action_view_delegate());

  // Finally, use the correct modifier flag, and verify the delegate is told
  // to move the action by the correct index.
  constexpr int kCorrectModifierFlag =
#if BUILDFLAG(IS_MAC)
      ui::EF_COMMAND_DOWN;
#else
      ui::EF_CONTROL_DOWN;
#endif
  EXPECT_CALL(mock_action_view_delegate(),
              MovePinnedActionBy(view_controller->GetId(), 1))
      .Times(1);
  send_key_press(ui::VKEY_RIGHT, kCorrectModifierFlag);
  testing::Mock::VerifyAndClearExpectations(&mock_action_view_delegate());

  EXPECT_CALL(mock_action_view_delegate(),
              MovePinnedActionBy(view_controller->GetId(), -1))
      .Times(1);
  send_key_press(ui::VKEY_LEFT, kCorrectModifierFlag);
  testing::Mock::VerifyAndClearExpectations(&mock_action_view_delegate());

  Reset();
}