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 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.h"
7 #include "base/command_line.h"
8 #include "base/run_loop.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
12 #include "chrome/browser/extensions/extension_action.h"
13 #include "chrome/browser/extensions/extension_action_manager.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/test_extension_system.h"
16 #include "chrome/browser/sessions/session_tab_helper.h"
17 #include "chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
20 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
21 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h"
22 #include "chrome/common/pref_names.h"
23 #include "extensions/browser/extension_prefs.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/common/extension.h"
29 // Verifies that the toolbar order matches for the given |actions_bar|. If the
30 // order matches, the return value is empty; otherwise, it contains the error.
31 std::string
VerifyToolbarOrderForBar(
32 const ToolbarActionsBar
* actions_bar
,
33 BrowserActionTestUtil
* browser_action_test_util
,
34 const char* expected_names
[],
36 size_t visible_count
) {
37 const std::vector
<ToolbarActionViewController
*>& toolbar_actions
=
38 actions_bar
->toolbar_actions_unordered();
39 // If the total size is wrong, we risk segfaulting by continuing. Abort now.
40 if (total_size
!= toolbar_actions
.size()) {
41 return base::StringPrintf("Incorrect action count: expected %d, found %d",
42 static_cast<int>(total_size
),
43 static_cast<int>(toolbar_actions
.size()));
46 // Check that the ToolbarActionsBar matches the expected state.
48 for (size_t i
= 0; i
< total_size
; ++i
) {
49 if (std::string(expected_names
[i
]) !=
50 base::UTF16ToUTF8(toolbar_actions
[i
]->GetActionName())) {
51 error
+= base::StringPrintf(
52 "Incorrect action in bar at index %d: expected '%s', found '%s'.\n",
55 base::UTF16ToUTF8(toolbar_actions
[i
]->GetActionName()).c_str());
58 size_t icon_count
= actions_bar
->GetIconCount();
59 if (visible_count
!= icon_count
)
60 error
+= base::StringPrintf(
61 "Incorrect visible count: expected %d, found %d.\n",
62 static_cast<int>(visible_count
), static_cast<int>(icon_count
));
64 // Test that the (platform-specific) toolbar view matches the expected state.
65 for (size_t i
= 0; i
< total_size
; ++i
) {
66 std::string id
= browser_action_test_util
->GetExtensionId(i
);
67 if (id
!= toolbar_actions
[i
]->GetId()) {
68 error
+= base::StringPrintf(
69 "Incorrect action in view at index %d: expected '%s', found '%s'.\n",
71 toolbar_actions
[i
]->GetId().c_str(),
75 size_t view_icon_count
= browser_action_test_util
->VisibleBrowserActions();
76 if (visible_count
!= view_icon_count
)
77 error
+= base::StringPrintf(
78 "Incorrect visible count in view: expected %d, found %d.\n",
79 static_cast<int>(visible_count
), static_cast<int>(view_icon_count
));
86 ToolbarActionsBarUnitTest::ToolbarActionsBarUnitTest()
87 : toolbar_model_(nullptr),
88 use_redesign_(false) {}
90 ToolbarActionsBarUnitTest::ToolbarActionsBarUnitTest(bool use_redesign
)
91 : toolbar_model_(nullptr),
92 use_redesign_(use_redesign
) {}
94 ToolbarActionsBarUnitTest::~ToolbarActionsBarUnitTest() {}
96 void ToolbarActionsBarUnitTest::SetUp() {
98 redesign_switch_
.reset(new extensions::FeatureSwitch::ScopedOverride(
99 extensions::FeatureSwitch::extension_action_redesign(), true));
102 BrowserWithTestWindowTest::SetUp();
103 // The toolbar typically displays extension icons, so create some extension
104 // test infrastructure.
105 extensions::TestExtensionSystem
* extension_system
=
106 static_cast<extensions::TestExtensionSystem
*>(
107 extensions::ExtensionSystem::Get(profile()));
108 extension_system
->CreateExtensionService(
109 base::CommandLine::ForCurrentProcess(),
113 extensions::extension_action_test_util::CreateToolbarModelForProfile(
116 ToolbarActionsBar::disable_animations_for_testing_
= true;
117 browser_action_test_util_
.reset(new BrowserActionTestUtil(browser(), false));
120 overflow_browser_action_test_util_
=
121 browser_action_test_util_
->CreateOverflowBar();
125 void ToolbarActionsBarUnitTest::TearDown() {
126 // Since the profile gets destroyed in BrowserWithTestWindowTest::TearDown(),
127 // we need to delete this now.
128 browser_action_test_util_
.reset();
129 overflow_browser_action_test_util_
.reset();
130 ToolbarActionsBar::disable_animations_for_testing_
= false;
131 redesign_switch_
.reset();
132 BrowserWithTestWindowTest::TearDown();
135 void ToolbarActionsBarUnitTest::ActivateTab(int index
) {
136 ASSERT_NE(nullptr, browser()->tab_strip_model()->GetWebContentsAt(index
));
137 browser()->tab_strip_model()->ActivateTabAt(index
, true);
140 scoped_refptr
<const extensions::Extension
>
141 ToolbarActionsBarUnitTest::CreateAndAddExtension(
142 const std::string
& name
,
143 extensions::extension_action_test_util::ActionType action_type
) {
144 scoped_refptr
<const extensions::Extension
> extension
=
145 extensions::extension_action_test_util::CreateActionExtension(
147 extensions::ExtensionSystem::Get(profile())->extension_service()->
148 AddExtension(extension
.get());
152 void ToolbarActionsBarUnitTest::SetActionWantsToRunOnTab(
153 ExtensionAction
* action
,
154 content::WebContents
* web_contents
,
156 action
->SetIsVisible(SessionTabHelper::IdForTab(web_contents
), wants_to_run
);
157 extensions::ExtensionActionAPI::Get(profile())->NotifyChange(
158 action
, web_contents
, profile());
161 testing::AssertionResult
ToolbarActionsBarUnitTest::VerifyToolbarOrder(
162 const char* expected_names
[],
164 size_t visible_count
) {
165 std::string main_bar_error
=
166 VerifyToolbarOrderForBar(toolbar_actions_bar(),
167 browser_action_test_util(),
171 std::string overflow_bar_error
;
174 VerifyToolbarOrderForBar(overflow_bar(),
175 overflow_browser_action_test_util(),
178 total_size
- visible_count
);
182 return main_bar_error
.empty() && overflow_bar_error
.empty() ?
183 testing::AssertionSuccess() :
184 testing::AssertionFailure() << "main bar error:\n" << main_bar_error
<<
185 "overflow bar error:\n" << overflow_bar_error
;
188 ToolbarActionsBarRedesignUnitTest::ToolbarActionsBarRedesignUnitTest()
189 : ToolbarActionsBarUnitTest(true) {}
191 ToolbarActionsBarRedesignUnitTest::~ToolbarActionsBarRedesignUnitTest() {}
193 TEST_F(ToolbarActionsBarUnitTest
, BasicToolbarActionsBarTest
) {
194 // Add three extensions to the profile; this is the easiest way to have
196 for (int i
= 0; i
< 3; ++i
) {
197 CreateAndAddExtension(
198 base::StringPrintf("extension %d", i
),
199 extensions::extension_action_test_util::BROWSER_ACTION
);
202 const ToolbarActionsBar::PlatformSettings
& platform_settings
=
203 toolbar_actions_bar()->platform_settings();
205 // By default, all three actions should be visible.
206 EXPECT_EQ(3u, toolbar_actions_bar()->GetIconCount());
208 int expected_width
= 3 * ToolbarActionsBar::IconWidth(true) -
209 platform_settings
.item_spacing
+
210 platform_settings
.left_padding
+
211 platform_settings
.right_padding
;
212 EXPECT_EQ(expected_width
, toolbar_actions_bar()->GetPreferredSize().width());
213 // Since all icons are showing, the current width should be the max width.
214 int maximum_width
= expected_width
;
215 EXPECT_EQ(maximum_width
, toolbar_actions_bar()->GetMaximumWidth());
216 // The minimum width should be just enough for the chevron to be displayed.
217 int minimum_width
= platform_settings
.left_padding
+
218 platform_settings
.right_padding
+
219 toolbar_actions_bar()->delegate_for_test()->
221 EXPECT_EQ(minimum_width
, toolbar_actions_bar()->GetMinimumWidth());
223 // Test the connection between the ToolbarActionsBar and the model by
224 // adjusting the visible count.
225 toolbar_model()->SetVisibleIconCount(2u);
226 EXPECT_EQ(2u, toolbar_actions_bar()->GetIconCount());
228 // The current width should now be enough for two icons, and the chevron.
229 expected_width
= 2 * ToolbarActionsBar::IconWidth(true) -
230 platform_settings
.item_spacing
+
231 platform_settings
.left_padding
+
232 platform_settings
.right_padding
+
233 toolbar_actions_bar()->delegate_for_test()->
235 EXPECT_EQ(expected_width
, toolbar_actions_bar()->GetPreferredSize().width());
236 // The maximum and minimum widths should have remained constant (since we have
237 // the same number of actions).
238 EXPECT_EQ(maximum_width
, toolbar_actions_bar()->GetMaximumWidth());
239 EXPECT_EQ(minimum_width
, toolbar_actions_bar()->GetMinimumWidth());
241 // Test drag-and-drop logic.
242 const char kExtension0
[] = "extension 0";
243 const char kExtension1
[] = "extension 1";
244 const char kExtension2
[] = "extension 2";
247 // The order should start as 0, 1, 2.
248 const char* expected_names
[] = { kExtension0
, kExtension1
, kExtension2
};
249 EXPECT_TRUE(VerifyToolbarOrder(expected_names
, 3u, 2u));
253 // Drag 0 to be in the second spot; 1, 0, 2, within the same container.
254 toolbar_actions_bar()->OnDragDrop(0, 1, ToolbarActionsBar::DRAG_TO_SAME
);
255 const char* expected_names
[] = { kExtension1
, kExtension0
, kExtension2
};
256 EXPECT_TRUE(VerifyToolbarOrder(expected_names
, 3u, 2u));
260 // Drag 0 to be in the third spot, in the overflow container.
261 // Order should be 1, 2, 0, and the icon count should reduce by 1.
262 toolbar_actions_bar()->OnDragDrop(
263 1, 2, ToolbarActionsBar::DRAG_TO_OVERFLOW
);
264 const char* expected_names
[] = { kExtension1
, kExtension2
, kExtension0
};
265 EXPECT_TRUE(VerifyToolbarOrder(expected_names
, 3u, 1u));
266 // The model should also reflect the updated icon count.
267 EXPECT_EQ(1u, toolbar_model()->visible_icon_count());
268 // Dragging 2 to the main container should work, even if its spot in the
269 // "list" remains constant.
270 // Order remains 1, 2, 0, but now we have 2 icons visible.
271 toolbar_actions_bar()->OnDragDrop(1, 1, ToolbarActionsBar::DRAG_TO_MAIN
);
272 EXPECT_TRUE(VerifyToolbarOrder(expected_names
, 3u, 2u));
273 // Similarly, dragging 2 to overflow, with the same "list" spot, should also
274 // work. Order remains 1, 2, 0, but icon count goes back to 1.
275 toolbar_actions_bar()->OnDragDrop(
276 1, 1, ToolbarActionsBar::DRAG_TO_OVERFLOW
);
277 EXPECT_TRUE(VerifyToolbarOrder(expected_names
, 3u, 1u));
280 // Try resizing the toolbar. Start with the current width (enough for 1 icon).
281 int width
= toolbar_actions_bar()->GetPreferredSize().width();
283 // If we try to resize by increasing, without allowing enough room for a new
284 // icon, width, and icon count should stay the same.
285 toolbar_actions_bar()->OnResizeComplete(width
+ 1);
286 EXPECT_EQ(width
, toolbar_actions_bar()->GetPreferredSize().width());
287 EXPECT_EQ(1u, toolbar_actions_bar()->GetIconCount());
289 // If we resize by enough to include a new icon, width and icon count should
291 width
+= ToolbarActionsBar::IconWidth(true);
292 toolbar_actions_bar()->OnResizeComplete(width
);
293 EXPECT_EQ(width
, toolbar_actions_bar()->GetPreferredSize().width());
294 EXPECT_EQ(2u, toolbar_actions_bar()->GetIconCount());
296 // If we shrink the bar so that a full icon can't fit, it should resize to
298 toolbar_actions_bar()->OnResizeComplete(width
- 1);
299 width
-= ToolbarActionsBar::IconWidth(true);
300 EXPECT_EQ(width
, toolbar_actions_bar()->GetPreferredSize().width());
301 EXPECT_EQ(1u, toolbar_actions_bar()->GetIconCount());
304 TEST_F(ToolbarActionsBarUnitTest
, ToolbarActionsReorderOnPrefChange
) {
305 for (int i
= 0; i
< 3; ++i
) {
306 CreateAndAddExtension(
307 base::StringPrintf("extension %d", i
),
308 extensions::extension_action_test_util::BROWSER_ACTION
);
310 EXPECT_EQ(3u, toolbar_actions_bar()->GetIconCount());
311 // Change the value of the toolbar preference.
312 // Test drag-and-drop logic.
313 const char kExtension0
[] = "extension 0";
314 const char kExtension1
[] = "extension 1";
315 const char kExtension2
[] = "extension 2";
317 // The order should start as 0, 1, 2.
318 const char* expected_names
[] = { kExtension0
, kExtension1
, kExtension2
};
319 EXPECT_TRUE(VerifyToolbarOrder(expected_names
, 3u, 3u));
322 std::vector
<std::string
> new_order
;
323 new_order
.push_back(toolbar_actions_bar()->toolbar_actions_unordered()[1]->
325 new_order
.push_back(toolbar_actions_bar()->toolbar_actions_unordered()[2]->
327 extensions::ExtensionPrefs::Get(profile())->SetToolbarOrder(new_order
);
330 // The order should now reflect the prefs, and be 1, 2, 0.
331 const char* expected_names
[] = { kExtension1
, kExtension2
, kExtension0
};
332 EXPECT_TRUE(VerifyToolbarOrder(expected_names
, 3u, 3u));
336 TEST_F(ToolbarActionsBarRedesignUnitTest
, IconSurfacingBubbleAppearance
) {
337 // Without showing anything new, we shouldn't show the bubble, and should
338 // auto-acknowledge it.
340 ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
342 PrefService
* prefs
= profile()->GetPrefs();
344 prefs
->GetBoolean(prefs::kToolbarIconSurfacingBubbleAcknowledged
));
346 // Clear the pref for testing, and add an extension that wouldn't normally
347 // have an icon. We should now show the bubble.
348 prefs
->ClearPref(prefs::kToolbarIconSurfacingBubbleAcknowledged
);
349 CreateAndAddExtension("extension",
350 extensions::extension_action_test_util::NO_ACTION
);
351 EXPECT_TRUE(ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
354 // If the bubble was recently shown, we shouldn't show it again...
355 scoped_ptr
<ToolbarActionsBarBubbleDelegate
> bubble_delegate(
356 new ExtensionToolbarIconSurfacingBubbleDelegate(profile()));
357 bubble_delegate
->OnBubbleShown();
358 bubble_delegate
->OnBubbleClosed(
359 ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS
);
361 ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
364 // ...But if it was only dismissed, we should show it before too long.
365 base::Time two_days_ago
= base::Time::Now() - base::TimeDelta::FromDays(2);
366 prefs
->SetInt64(prefs::kToolbarIconSurfacingBubbleLastShowTime
,
367 two_days_ago
.ToInternalValue());
368 EXPECT_TRUE(ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
371 // If it's acknowledged, then it should never show again, and should be
372 // recorded as acknowledged.
373 bubble_delegate
->OnBubbleShown();
374 bubble_delegate
->OnBubbleClosed(
375 ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE
);
377 ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
379 base::Time one_week_ago
= base::Time::Now() - base::TimeDelta::FromDays(7);
380 prefs
->SetInt64(prefs::kToolbarIconSurfacingBubbleLastShowTime
,
381 one_week_ago
.ToInternalValue());
383 prefs
->GetBoolean(prefs::kToolbarIconSurfacingBubbleAcknowledged
));
386 // Test the bounds calculation for different indices.
387 TEST_F(ToolbarActionsBarRedesignUnitTest
, TestActionFrameBounds
) {
388 const int kIconWidth
= ToolbarActionsBar::IconWidth(false);
389 const int kIconHeight
= ToolbarActionsBar::IconHeight();
390 const int kIconWidthWithPadding
= ToolbarActionsBar::IconWidth(true);
391 const int kIconsPerOverflowRow
= 3;
392 const int kNumExtensions
= 7;
394 toolbar_actions_bar()->platform_settings().item_spacing
;
396 // Initialization: 7 total extensions, with 3 visible per row in overflow.
397 // Start with all visible on the main bar.
398 for (int i
= 0; i
< kNumExtensions
; ++i
) {
399 CreateAndAddExtension(
400 base::StringPrintf("extension %d", i
),
401 extensions::extension_action_test_util::BROWSER_ACTION
);
403 toolbar_model()->SetVisibleIconCount(kNumExtensions
);
404 overflow_bar()->SetOverflowRowWidth(
405 kIconWidthWithPadding
* kIconsPerOverflowRow
+ 3);
406 EXPECT_EQ(kIconsPerOverflowRow
,
407 overflow_bar()->platform_settings().icons_per_overflow_menu_row
);
409 // Check main bar calculations. Actions should be laid out in a line, so
410 // all on the same (0) y-axis.
411 EXPECT_EQ(gfx::Rect(kSpacing
, 0, kIconWidth
, kIconHeight
),
412 toolbar_actions_bar()->GetFrameForIndex(0));
413 EXPECT_EQ(gfx::Rect(kSpacing
+ kIconWidthWithPadding
, 0, kIconWidth
,
415 toolbar_actions_bar()->GetFrameForIndex(1));
416 EXPECT_EQ(gfx::Rect(kSpacing
+ kIconWidthWithPadding
* (kNumExtensions
- 1),
417 0, kIconWidth
, kIconHeight
),
418 toolbar_actions_bar()->GetFrameForIndex(kNumExtensions
- 1));
420 // Check overflow bar calculations.
421 toolbar_model()->SetVisibleIconCount(3);
422 // Any actions that are shown on the main bar should have an empty rect for
424 EXPECT_EQ(gfx::Rect(), overflow_bar()->GetFrameForIndex(0));
425 EXPECT_EQ(gfx::Rect(), overflow_bar()->GetFrameForIndex(2));
427 // Other actions should start from their relative index; that is, the first
428 // action shown should be in the first spot's bounds, even though it's the
429 // third action by index.
430 EXPECT_EQ(gfx::Rect(kSpacing
, 0, kIconWidth
, kIconHeight
),
431 overflow_bar()->GetFrameForIndex(3));
432 EXPECT_EQ(gfx::Rect(kSpacing
+ kIconWidthWithPadding
, 0, kIconWidth
,
434 overflow_bar()->GetFrameForIndex(4));
435 EXPECT_EQ(gfx::Rect(kSpacing
+ kIconWidthWithPadding
* 2, 0, kIconWidth
,
437 overflow_bar()->GetFrameForIndex(5));
438 // And the actions should wrap, so that it starts back at the left on a new
440 EXPECT_EQ(gfx::Rect(kSpacing
, kIconHeight
, kIconWidth
, kIconHeight
),
441 overflow_bar()->GetFrameForIndex(6));
443 // Check with > 2 rows.
444 toolbar_model()->SetVisibleIconCount(0);
445 EXPECT_EQ(gfx::Rect(kSpacing
, 0, kIconWidth
, kIconHeight
),
446 overflow_bar()->GetFrameForIndex(0));
447 EXPECT_EQ(gfx::Rect(kSpacing
, kIconHeight
* 2, kIconWidth
, kIconHeight
),
448 overflow_bar()->GetFrameForIndex(6));