1 // Copyright 2019 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.
4
5 #include "chrome/browser/ui/views/extensions/extensions_menu_view.h"
6
7 #include <memory>
8 #include <string>
9 #include <vector>
10
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/test/metrics/user_action_tester.h"
16 #include "build/build_config.h"
17 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
18 #include "chrome/browser/extensions/extension_action_test_util.h"
19 #include "chrome/browser/extensions/extension_service.h"
20 #include "chrome/browser/extensions/load_error_reporter.h"
21 #include "chrome/browser/extensions/test_extension_system.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
24 #include "chrome/browser/ui/views/extensions/extensions_menu_button.h"
25 #include "chrome/browser/ui/views/extensions/extensions_menu_item_view.h"
26 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
27 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
28 #include "chrome/browser/ui/views/frame/browser_view.h"
29 #include "chrome/browser/ui/views/frame/test_with_browser_view.h"
30 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
31 #include "content/public/test/test_utils.h"
32 #include "extensions/browser/disable_reason.h"
33 #include "extensions/browser/extension_system.h"
34 #include "extensions/browser/test_extension_registry_observer.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/extension_builder.h"
37 #include "extensions/test/test_extension_dir.h"
38 #include "testing/gmock/include/gmock/gmock.h"
39 #include "ui/events/base_event_utils.h"
40 #include "ui/events/event.h"
41 #include "ui/events/event_constants.h"
42 #include "ui/events/types/event_type.h"
43 #include "ui/gfx/geometry/point.h"
44 #include "ui/views/controls/button/image_button.h"
45 #include "ui/views/layout/animating_layout_manager_test_util.h"
46 #include "ui/views/widget/widget.h"
47
48 namespace {
49
50 // Manages a Browser instance created by BrowserWithTestWindowTest beyond the
51 // default instance it creates in SetUp.
52 class AdditionalBrowser {
53 public:
AdditionalBrowser(std::unique_ptr<Browser> browser)54 explicit AdditionalBrowser(std::unique_ptr<Browser> browser)
55 : browser_(std::move(browser)),
56 browser_view_(BrowserView::GetBrowserViewForBrowser(browser_.get())) {}
57
~AdditionalBrowser()58 ~AdditionalBrowser() {
59 // Tear down |browser_|, similar to TestWithBrowserView::TearDown.
60 browser_.release();
61 browser_view_->GetWidget()->CloseNow();
62 }
63
extensions_container()64 ExtensionsToolbarContainer* extensions_container() {
65 return browser_view_->toolbar()->extensions_container();
66 }
67
68 private:
69 std::unique_ptr<Browser> browser_;
70 BrowserView* browser_view_;
71 };
72
73 } // namespace
74
75 class ExtensionsMenuViewUnitTest : public TestWithBrowserView {
76 public:
ExtensionsMenuViewUnitTest()77 ExtensionsMenuViewUnitTest()
78 : allow_extension_menu_instances_(
79 ExtensionsMenuView::AllowInstancesForTesting()) {}
80 ~ExtensionsMenuViewUnitTest() override = default;
81
82 // TestWithBrowserView:
83 void SetUp() override;
84
85 // Adds a simple extension to the profile.
86 scoped_refptr<const extensions::Extension> AddSimpleExtension(
87 const std::string& name);
88
extension_service()89 extensions::ExtensionService* extension_service() {
90 return extension_service_;
91 }
92
extensions_container()93 ExtensionsToolbarContainer* extensions_container() {
94 return browser_view()->toolbar()->extensions_container();
95 }
96
extensions_menu()97 ExtensionsMenuView* extensions_menu() {
98 return ExtensionsMenuView::GetExtensionsMenuViewForTesting();
99 }
100
101 // Asserts there is exactly 1 menu item and then returns it.
102 ExtensionsMenuItemView* GetOnlyMenuItem();
103
104 void ClickPinButton(ExtensionsMenuItemView* menu_item) const;
105 void ClickContextMenuButton(ExtensionsMenuItemView* menu_item) const;
106 void ClickButton(views::Button* button) const;
107
108 std::vector<ToolbarActionView*> GetPinnedExtensionViews();
109
110 ToolbarActionView* GetPinnedExtensionView(const std::string& name);
111
112 // Returns a list of the names of the currently pinned extensions, in order
113 // from left to right.
114 std::vector<std::string> GetPinnedExtensionNames();
115
116 // Since this is a unittest (and doesn't have as much "real" rendering),
117 // the ExtensionsMenuView sometimes needs a nudge to re-layout the views.
118 void LayoutMenuIfNecessary();
119
120 // Waits for the extensions container to animate (on pin, unpin, pop-out,
121 // etc.)
122 void WaitForAnimation();
123
124 private:
125 base::AutoReset<bool> allow_extension_menu_instances_;
126
127 extensions::ExtensionService* extension_service_ = nullptr;
128
129 DISALLOW_COPY_AND_ASSIGN(ExtensionsMenuViewUnitTest);
130 };
131
SetUp()132 void ExtensionsMenuViewUnitTest::SetUp() {
133 TestWithBrowserView::SetUp();
134
135 // Set up some extension-y bits.
136 extensions::TestExtensionSystem* extension_system =
137 static_cast<extensions::TestExtensionSystem*>(
138 extensions::ExtensionSystem::Get(profile()));
139 extension_system->CreateExtensionService(
140 base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
141
142 extension_service_ =
143 extensions::ExtensionSystem::Get(profile())->extension_service();
144
145 // Shorten delay on animations so tests run faster.
146 views::test::ReduceAnimationDuration(extensions_container());
147
148 ExtensionsMenuView::ShowBubble(extensions_container()->extensions_button(),
149 browser(), extensions_container(), true);
150 }
151
152 scoped_refptr<const extensions::Extension>
AddSimpleExtension(const std::string & name)153 ExtensionsMenuViewUnitTest::AddSimpleExtension(const std::string& name) {
154 scoped_refptr<const extensions::Extension> extension =
155 extensions::ExtensionBuilder(name).Build();
156 extension_service()->AddExtension(extension.get());
157 // Force the menu to re-layout, since a new item was added.
158 LayoutMenuIfNecessary();
159
160 return extension;
161 }
162
GetOnlyMenuItem()163 ExtensionsMenuItemView* ExtensionsMenuViewUnitTest::GetOnlyMenuItem() {
164 std::vector<ExtensionsMenuItemView*> menu_items =
165 extensions_menu()->extensions_menu_items_for_testing();
166 if (menu_items.size() != 1u) {
167 ADD_FAILURE() << "Not exactly one item; size is: " << menu_items.size();
168 return nullptr;
169 }
170 return menu_items[0];
171 }
172
ClickPinButton(ExtensionsMenuItemView * menu_item) const173 void ExtensionsMenuViewUnitTest::ClickPinButton(
174 ExtensionsMenuItemView* menu_item) const {
175 ClickButton(menu_item->pin_button_for_testing());
176 }
177
ClickContextMenuButton(ExtensionsMenuItemView * menu_item) const178 void ExtensionsMenuViewUnitTest::ClickContextMenuButton(
179 ExtensionsMenuItemView* menu_item) const {
180 ClickButton(menu_item->context_menu_button_for_testing());
181 }
182
ClickButton(views::Button * button) const183 void ExtensionsMenuViewUnitTest::ClickButton(views::Button* button) const {
184 ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, gfx::Point(1, 1),
185 gfx::Point(), ui::EventTimeForNow(),
186 ui::EF_LEFT_MOUSE_BUTTON, 0);
187 button->OnMousePressed(press_event);
188 ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, gfx::Point(1, 1),
189 gfx::Point(), ui::EventTimeForNow(),
190 ui::EF_LEFT_MOUSE_BUTTON, 0);
191 button->OnMouseReleased(release_event);
192 }
193
194 std::vector<ToolbarActionView*>
GetPinnedExtensionViews()195 ExtensionsMenuViewUnitTest::GetPinnedExtensionViews() {
196 std::vector<ToolbarActionView*> result;
197 for (views::View* child : extensions_container()->children()) {
198 // Ensure we don't downcast the ExtensionsToolbarButton.
199 if (child->GetClassName() == ToolbarActionView::kClassName) {
200 ToolbarActionView* const action = static_cast<ToolbarActionView*>(child);
201 #if defined(OS_MAC)
202 // TODO(crbug.com/1045212): Use IsActionVisibleOnToolbar() because it
203 // queries the underlying model and not GetVisible(), as that relies on an
204 // animation running, which is not reliable in unit tests on Mac.
205 const bool is_visible = extensions_container()->IsActionVisibleOnToolbar(
206 action->view_controller());
207 #else
208 const bool is_visible = action->GetVisible();
209 #endif
210 if (is_visible)
211 result.push_back(action);
212 }
213 }
214 return result;
215 }
216
GetPinnedExtensionView(const std::string & name)217 ToolbarActionView* ExtensionsMenuViewUnitTest::GetPinnedExtensionView(
218 const std::string& name) {
219 std::vector<ToolbarActionView*> actions = GetPinnedExtensionViews();
220 auto it = std::find_if(
221 actions.begin(), actions.end(), [name](ToolbarActionView* action) {
222 return base::UTF16ToUTF8(action->view_controller()->GetActionName()) ==
223 name;
224 });
225 if (it == actions.end())
226 return nullptr;
227 return *it;
228 }
229
GetPinnedExtensionNames()230 std::vector<std::string> ExtensionsMenuViewUnitTest::GetPinnedExtensionNames() {
231 std::vector<ToolbarActionView*> views = GetPinnedExtensionViews();
232 std::vector<std::string> result;
233 result.resize(views.size());
234 std::transform(
235 views.begin(), views.end(), result.begin(), [](ToolbarActionView* view) {
236 return base::UTF16ToUTF8(view->view_controller()->GetActionName());
237 });
238 return result;
239 }
240
LayoutMenuIfNecessary()241 void ExtensionsMenuViewUnitTest::LayoutMenuIfNecessary() {
242 extensions_menu()->GetWidget()->LayoutRootViewIfNecessary();
243 }
244
WaitForAnimation()245 void ExtensionsMenuViewUnitTest::WaitForAnimation() {
246 #if defined(OS_MAC)
247 // TODO(crbug.com/1045212): we avoid using animations on Mac due to the lack
248 // of support in unit tests. Therefore this is a no-op.
249 #else
250 views::test::WaitForAnimatingLayoutManager(extensions_container());
251 #endif
252 }
253
TEST_F(ExtensionsMenuViewUnitTest,ExtensionsAreShownInTheMenu)254 TEST_F(ExtensionsMenuViewUnitTest, ExtensionsAreShownInTheMenu) {
255 // To start, there should be no extensions in the menu.
256 EXPECT_EQ(0u, extensions_menu()->extensions_menu_items_for_testing().size());
257
258 // Add an extension, and verify that it's added to the menu.
259 constexpr char kExtensionName[] = "Test 1";
260 AddSimpleExtension(kExtensionName);
261
262 {
263 std::vector<ExtensionsMenuItemView*> menu_items =
264 extensions_menu()->extensions_menu_items_for_testing();
265 ASSERT_EQ(1u, menu_items.size());
266 EXPECT_EQ(kExtensionName,
267 base::UTF16ToUTF8(menu_items[0]
268 ->primary_action_button_for_testing()
269 ->label_text_for_testing()));
270 }
271 }
272
TEST_F(ExtensionsMenuViewUnitTest,PinnedExtensionAppearsInToolbar)273 TEST_F(ExtensionsMenuViewUnitTest, PinnedExtensionAppearsInToolbar) {
274 constexpr char kName[] = "Test Name";
275 AddSimpleExtension(kName);
276
277 ExtensionsMenuItemView* menu_item = GetOnlyMenuItem();
278 ASSERT_TRUE(menu_item);
279 ToolbarActionViewController* controller = menu_item->view_controller();
280 EXPECT_FALSE(extensions_container()->IsActionVisibleOnToolbar(controller));
281 EXPECT_THAT(GetPinnedExtensionNames(), testing::IsEmpty());
282
283 ClickPinButton(menu_item);
284 WaitForAnimation();
285
286 EXPECT_TRUE(extensions_container()->IsActionVisibleOnToolbar(controller));
287 EXPECT_THAT(GetPinnedExtensionNames(), testing::ElementsAre(kName));
288
289 ClickPinButton(menu_item); // Unpin.
290 WaitForAnimation();
291
292 EXPECT_FALSE(extensions_container()->IsActionVisibleOnToolbar(
293 menu_item->view_controller()));
294 EXPECT_THAT(GetPinnedExtensionNames(), testing::IsEmpty());
295 }
296
TEST_F(ExtensionsMenuViewUnitTest,PinnedExtensionAppearsInAnotherWindow)297 TEST_F(ExtensionsMenuViewUnitTest, PinnedExtensionAppearsInAnotherWindow) {
298 AddSimpleExtension("Test Name");
299
300 AdditionalBrowser browser2(
301 CreateBrowser(browser()->profile(), browser()->type(),
302 /* hosted_app */ false, /* browser_window */ nullptr));
303
304 ExtensionsMenuItemView* menu_item = GetOnlyMenuItem();
305 ASSERT_TRUE(menu_item);
306 ClickPinButton(menu_item);
307
308 // Window that was already open gets the pinned extension.
309 browser2.extensions_container()->IsActionVisibleOnToolbar(
310 menu_item->view_controller());
311
312 AdditionalBrowser browser3(
313 CreateBrowser(browser()->profile(), browser()->type(),
314 /* hosted_app */ false, /* browser_window */ nullptr));
315
316 // Brand-new window also gets the pinned extension.
317 browser3.extensions_container()->IsActionVisibleOnToolbar(
318 menu_item->view_controller());
319 }
320
TEST_F(ExtensionsMenuViewUnitTest,PinnedExtensionRemovedWhenDisabled)321 TEST_F(ExtensionsMenuViewUnitTest, PinnedExtensionRemovedWhenDisabled) {
322 constexpr char kName[] = "Test Name";
323 const extensions::ExtensionId id = AddSimpleExtension(kName)->id();
324
325 {
326 ExtensionsMenuItemView* menu_item = GetOnlyMenuItem();
327 ASSERT_TRUE(menu_item);
328 ClickPinButton(menu_item);
329 }
330
331 extension_service()->DisableExtension(
332 id, extensions::disable_reason::DISABLE_USER_ACTION);
333 WaitForAnimation();
334
335 ASSERT_EQ(0u, extensions_menu()->extensions_menu_items_for_testing().size());
336 EXPECT_THAT(GetPinnedExtensionNames(), testing::IsEmpty());
337
338 extension_service()->EnableExtension(id);
339 WaitForAnimation();
340
341 ASSERT_EQ(1u, extensions_menu()->extensions_menu_items_for_testing().size());
342 EXPECT_THAT(GetPinnedExtensionNames(), testing::ElementsAre(kName));
343 }
344
TEST_F(ExtensionsMenuViewUnitTest,ReorderPinnedExtensions)345 TEST_F(ExtensionsMenuViewUnitTest, ReorderPinnedExtensions) {
346 constexpr char kName1[] = "Test 1";
347 AddSimpleExtension(kName1);
348 constexpr char kName2[] = "Test 2";
349 AddSimpleExtension(kName2);
350 constexpr char kName3[] = "Test 3";
351 AddSimpleExtension(kName3);
352
353 std::vector<ExtensionsMenuItemView*> menu_items =
354 extensions_menu()->extensions_menu_items_for_testing();
355 ASSERT_EQ(3u, menu_items.size());
356 for (auto* menu_item : menu_items) {
357 ClickPinButton(menu_item);
358 EXPECT_TRUE(extensions_container()->IsActionVisibleOnToolbar(
359 menu_item->view_controller()));
360 }
361 WaitForAnimation();
362
363 EXPECT_THAT(GetPinnedExtensionNames(),
364 testing::ElementsAre(kName1, kName2, kName3));
365
366 // Simulate dragging "Test 3" to the first slot.
367 ToolbarActionView* drag_view = GetPinnedExtensionView(kName3);
368 ui::OSExchangeData drag_data;
369 extensions_container()->WriteDragDataForView(drag_view, gfx::Point(),
370 &drag_data);
371 gfx::PointF drop_point(GetPinnedExtensionView(kName1)->origin());
372 ui::DropTargetEvent drop_event(drag_data, drop_point, drop_point,
373 ui::DragDropTypes::DRAG_MOVE);
374 extensions_container()->OnDragUpdated(drop_event);
375 extensions_container()->OnPerformDrop(drop_event);
376 WaitForAnimation();
377
378 EXPECT_THAT(GetPinnedExtensionNames(),
379 testing::ElementsAre(kName3, kName1, kName2));
380 }
381
TEST_F(ExtensionsMenuViewUnitTest,PinnedExtensionsReorderOnPrefChange)382 TEST_F(ExtensionsMenuViewUnitTest, PinnedExtensionsReorderOnPrefChange) {
383 constexpr char kName1[] = "Test 1";
384 const extensions::ExtensionId id1 = AddSimpleExtension(kName1)->id();
385 constexpr char kName2[] = "Test 2";
386 const extensions::ExtensionId id2 = AddSimpleExtension(kName2)->id();
387 constexpr char kName3[] = "Test 3";
388 const extensions::ExtensionId id3 = AddSimpleExtension(kName3)->id();
389 for (auto* menu_item :
390 extensions_menu()->extensions_menu_items_for_testing()) {
391 ClickPinButton(menu_item);
392 }
393 WaitForAnimation();
394
395 EXPECT_THAT(GetPinnedExtensionNames(),
396 testing::ElementsAre(kName1, kName2, kName3));
397
398 extensions::ExtensionPrefs::Get(profile())->SetPinnedExtensions(
399 {id2, id3, id1});
400 WaitForAnimation();
401
402 EXPECT_THAT(GetPinnedExtensionNames(),
403 testing::ElementsAre(kName2, kName3, kName1));
404 }
405
TEST_F(ExtensionsMenuViewUnitTest,PinnedExtensionLayout)406 TEST_F(ExtensionsMenuViewUnitTest, PinnedExtensionLayout) {
407 for (int i = 0; i < 3; i++)
408 AddSimpleExtension(base::StringPrintf("Test %d", i));
409 for (auto* menu_item :
410 extensions_menu()->extensions_menu_items_for_testing()) {
411 ClickPinButton(menu_item);
412 }
413 WaitForAnimation();
414
415 std::vector<ToolbarActionView*> action_views = GetPinnedExtensionViews();
416 ASSERT_EQ(3u, action_views.size());
417 ExtensionsToolbarButton* menu_button =
418 extensions_container()->extensions_button();
419
420 // All views should be lined up horizontally with the menu button.
421 EXPECT_EQ(action_views[0]->y(), action_views[1]->y());
422 EXPECT_EQ(action_views[1]->y(), action_views[2]->y());
423 EXPECT_EQ(action_views[2]->y(), menu_button->y());
424
425 // Views are ordered left-to-right (in LTR mode).
426 EXPECT_LE(action_views[0]->x() + action_views[0]->width(),
427 action_views[1]->x());
428 EXPECT_LE(action_views[1]->x() + action_views[1]->width(),
429 action_views[2]->x());
430 EXPECT_LE(action_views[2]->x() + action_views[2]->width(), menu_button->x());
431 }
432
433 // Tests that when an extension is reloaded it remains visible in the toolbar
434 // and extensions menu.
TEST_F(ExtensionsMenuViewUnitTest,ReloadExtension)435 TEST_F(ExtensionsMenuViewUnitTest, ReloadExtension) {
436 // The extension must have a manifest to be reloaded.
437 extensions::TestExtensionDir extension_directory;
438 constexpr char kManifest[] = R"({
439 "name": "Test",
440 "version": "1",
441 "manifest_version": 2
442 })";
443 extension_directory.WriteManifest(kManifest);
444 extensions::ChromeTestExtensionLoader loader(profile());
445 scoped_refptr<const extensions::Extension> extension =
446 loader.LoadExtension(extension_directory.UnpackedPath());
447 // Force the menu to re-layout, since a new item was added.
448 LayoutMenuIfNecessary();
449 ASSERT_EQ(1u, extensions_menu()->extensions_menu_items_for_testing().size());
450
451 {
452 ExtensionsMenuItemView* menu_item = GetOnlyMenuItem();
453 ClickPinButton(menu_item);
454 EXPECT_TRUE(extensions_container()->IsActionVisibleOnToolbar(
455 menu_item->view_controller()));
456 // |menu_item| will not be valid after the extension reloads.
457 }
458
459 extensions::TestExtensionRegistryObserver registry_observer(
460 extensions::ExtensionRegistry::Get(profile()));
461 extension_service()->ReloadExtension(extension->id());
462 ASSERT_TRUE(registry_observer.WaitForExtensionLoaded());
463 LayoutMenuIfNecessary();
464
465 ASSERT_EQ(1u, extensions_menu()->extensions_menu_items_for_testing().size());
466 EXPECT_TRUE(extensions_container()->IsActionVisibleOnToolbar(
467 GetOnlyMenuItem()->view_controller()));
468 }
469
470 // Tests that a when an extension is reloaded with manifest errors, and
471 // therefore fails to be loaded into Chrome, it's removed from the toolbar and
472 // extensions menu.
TEST_F(ExtensionsMenuViewUnitTest,ReloadExtensionFailed)473 TEST_F(ExtensionsMenuViewUnitTest, ReloadExtensionFailed) {
474 extensions::TestExtensionDir extension_directory;
475 constexpr char kManifest[] = R"({
476 "name": "Test",
477 "version": "1",
478 "manifest_version": 2
479 })";
480 extension_directory.WriteManifest(kManifest);
481 extensions::ChromeTestExtensionLoader loader(profile());
482 scoped_refptr<const extensions::Extension> extension =
483 loader.LoadExtension(extension_directory.UnpackedPath());
484 LayoutMenuIfNecessary();
485 ExtensionsMenuItemView* menu_item = GetOnlyMenuItem();
486 ASSERT_TRUE(menu_item);
487 ClickPinButton(menu_item);
488
489 // Replace the extension's valid manifest with one containing errors. In this
490 // case, the error is that both the 'browser_action' and 'page_action' keys
491 // are specified instead of only one.
492 constexpr char kManifestWithErrors[] = R"({
493 "name": "Test",
494 "version": "1",
495 "manifest_version": 2,
496 "page_action" : {},
497 "browser_action" : {}
498 })";
499 extension_directory.WriteManifest(kManifestWithErrors);
500
501 // Reload the extension. It should fail due to the manifest errors.
502 extension_service()->ReloadExtensionWithQuietFailure(extension->id());
503 base::RunLoop().RunUntilIdle();
504
505 // Since the extension is removed it's no longer visible on the toolbar or in
506 // the menu.
507 for (views::View* child : extensions_container()->children())
508 EXPECT_NE(ToolbarActionView::kClassName, child->GetClassName());
509 EXPECT_EQ(0u, extensions_menu()->extensions_menu_items_for_testing().size());
510 }
511
TEST_F(ExtensionsMenuViewUnitTest,PinButtonUserAction)512 TEST_F(ExtensionsMenuViewUnitTest, PinButtonUserAction) {
513 base::UserActionTester user_action_tester;
514 AddSimpleExtension("Test Extension");
515
516 ExtensionsMenuItemView* menu_item = GetOnlyMenuItem();
517 ASSERT_TRUE(menu_item);
518
519 constexpr char kPinButtonUserAction[] = "Extensions.Toolbar.PinButtonPressed";
520 EXPECT_EQ(0, user_action_tester.GetActionCount(kPinButtonUserAction));
521 ClickPinButton(menu_item);
522 EXPECT_EQ(1, user_action_tester.GetActionCount(kPinButtonUserAction));
523 ClickPinButton(menu_item); // Unpin.
524 EXPECT_EQ(2, user_action_tester.GetActionCount(kPinButtonUserAction));
525 }
526
TEST_F(ExtensionsMenuViewUnitTest,ContextMenuButtonUserAction)527 TEST_F(ExtensionsMenuViewUnitTest, ContextMenuButtonUserAction) {
528 base::UserActionTester user_action_tester;
529 AddSimpleExtension("Test Extension");
530
531 ExtensionsMenuItemView* menu_item = GetOnlyMenuItem();
532 ASSERT_TRUE(menu_item);
533
534 constexpr char kContextMenuButtonUserAction[] =
535 "Extensions.Toolbar.MoreActionsButtonPressedFromMenu";
536 EXPECT_EQ(0, user_action_tester.GetActionCount(kContextMenuButtonUserAction));
537 ClickContextMenuButton(menu_item);
538 EXPECT_EQ(1, user_action_tester.GetActionCount(kContextMenuButtonUserAction));
539 }
540
541 // TODO(crbug.com/984654): When supported, add a test to verify the
542 // ExtensionsToolbarContainer shrinks when the window is too small to show all
543 // pinned extensions.
544 // TODO(crbug.com/984654): When supported, add a test to verify an extension
545 // is shown when a bubble pops up and needs to draw attention to it.
546