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