frame_caption_button_container_view_unittest.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // Copyright 2013 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. #include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
  5. #include "ash/shell.h"
  6. #include "ash/test/ash_test_base.h"
  7. #include "ash/wm/float/float_controller.h"
  8. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  9. #include "ash/wm/window_state.h"
  10. #include "ash/wm/wm_event.h"
  11. #include "base/test/bind.h"
  12. #include "base/test/scoped_feature_list.h"
  13. #include "chromeos/ui/base/window_properties.h"
  14. #include "chromeos/ui/base/window_state_type.h"
  15. #include "chromeos/ui/vector_icons/vector_icons.h"
  16. #include "chromeos/ui/wm/features.h"
  17. #include "ui/events/test/event_generator.h"
  18. #include "ui/gfx/geometry/rect.h"
  19. #include "ui/views/test/test_views.h"
  20. #include "ui/views/widget/widget.h"
  21. #include "ui/views/widget/widget_delegate.h"
  22. #include "ui/views/window/caption_button_layout_constants.h"
  23. #include "ui/views/window/frame_caption_button.h"
  24. #include "ui/views/window/vector_icons/vector_icons.h"
  25. namespace ash {
  26. using ::chromeos::FrameCaptionButtonContainerView;
  27. class FrameCaptionButtonContainerViewTest : public AshTestBase {
  28. public:
  29. enum MaximizeAllowed { MAXIMIZE_ALLOWED, MAXIMIZE_DISALLOWED };
  30. enum MinimizeAllowed { MINIMIZE_ALLOWED, MINIMIZE_DISALLOWED };
  31. enum CloseButtonVisible { CLOSE_BUTTON_VISIBLE, CLOSE_BUTTON_NOT_VISIBLE };
  32. FrameCaptionButtonContainerViewTest() = default;
  33. FrameCaptionButtonContainerViewTest(
  34. const FrameCaptionButtonContainerViewTest&) = delete;
  35. FrameCaptionButtonContainerViewTest& operator=(
  36. const FrameCaptionButtonContainerViewTest&) = delete;
  37. ~FrameCaptionButtonContainerViewTest() override = default;
  38. // Creates a widget which allows maximizing based on |maximize_allowed|.
  39. // The caller takes ownership of the returned widget.
  40. [[nodiscard]] views::Widget* CreateTestWidget(
  41. MaximizeAllowed maximize_allowed,
  42. MinimizeAllowed minimize_allowed,
  43. CloseButtonVisible close_button_visible) {
  44. views::Widget* widget = new views::Widget;
  45. views::Widget::InitParams params(
  46. views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  47. auto delegate = std::make_unique<views::WidgetDelegateView>();
  48. delegate->SetCanMaximize(maximize_allowed == MAXIMIZE_ALLOWED);
  49. delegate->SetCanMinimize(minimize_allowed == MINIMIZE_ALLOWED);
  50. delegate->SetCanResize(true);
  51. delegate->SetShowCloseButton(close_button_visible == CLOSE_BUTTON_VISIBLE);
  52. params.delegate = delegate.release();
  53. params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
  54. params.bounds = gfx::Rect(10, 10, 100, 100);
  55. params.context = GetContext();
  56. widget->Init(std::move(params));
  57. return widget;
  58. }
  59. // Sets arbitrary images for the icons and assign the default caption button
  60. // size to the buttons in |container|.
  61. void InitContainer(FrameCaptionButtonContainerView* container) {
  62. container->SetButtonSize(views::GetCaptionButtonLayoutSize(
  63. views::CaptionButtonLayoutSize::kNonBrowserCaption));
  64. for (int icon = 0; icon < views::CAPTION_BUTTON_ICON_COUNT; ++icon) {
  65. container->SetButtonImage(static_cast<views::CaptionButtonIcon>(icon),
  66. views::kWindowControlCloseIcon);
  67. }
  68. container->SizeToPreferredSize();
  69. }
  70. // Tests that |leftmost| and |rightmost| are at |container|'s edges.
  71. bool CheckButtonsAtEdges(FrameCaptionButtonContainerView* container,
  72. const views::FrameCaptionButton& leftmost,
  73. const views::FrameCaptionButton& rightmost) {
  74. gfx::Rect expected(container->GetPreferredSize());
  75. gfx::Rect container_size(container->GetPreferredSize());
  76. if (leftmost.y() == rightmost.y() &&
  77. leftmost.height() == rightmost.height() &&
  78. leftmost.x() == expected.x() && leftmost.y() == expected.y() &&
  79. leftmost.height() == expected.height() &&
  80. rightmost.bounds().right() == expected.right()) {
  81. return true;
  82. }
  83. LOG(ERROR) << "Buttons " << leftmost.bounds().ToString() << " "
  84. << rightmost.bounds().ToString() << " not at edges of "
  85. << expected.ToString();
  86. return false;
  87. }
  88. void ClickSizeButton(FrameCaptionButtonContainerView::TestApi* testApi) {
  89. ui::test::EventGenerator* generator = GetEventGenerator();
  90. generator->MoveMouseTo(
  91. testApi->size_button()->GetBoundsInScreen().CenterPoint());
  92. generator->ClickLeftButton();
  93. base::RunLoop().RunUntilIdle();
  94. }
  95. void ClickFloatButton(FrameCaptionButtonContainerView::TestApi* testApi) {
  96. ui::test::EventGenerator* generator = GetEventGenerator();
  97. auto* float_button = testApi->float_button();
  98. generator->MoveMouseTo(float_button->GetBoundsInScreen().CenterPoint());
  99. generator->ClickLeftButton();
  100. base::RunLoop().RunUntilIdle();
  101. }
  102. };
  103. // Test float button requires kFloatWindow feature to be enabled during setup.
  104. class WindowFloatButtonTest : public FrameCaptionButtonContainerViewTest {
  105. public:
  106. WindowFloatButtonTest() = default;
  107. WindowFloatButtonTest(const WindowFloatButtonTest&) = delete;
  108. WindowFloatButtonTest& operator=(const WindowFloatButtonTest&) = delete;
  109. ~WindowFloatButtonTest() override = default;
  110. void SetUp() override {
  111. // Ensure float feature is enabled.
  112. scoped_feature_list_.InitAndEnableFeature(
  113. chromeos::wm::features::kFloatWindow);
  114. AshTestBase::SetUp();
  115. }
  116. private:
  117. base::test::ScopedFeatureList scoped_feature_list_;
  118. };
  119. // Test how the allowed actions affect which caption buttons are visible.
  120. TEST_F(FrameCaptionButtonContainerViewTest, ButtonVisibility) {
  121. // All the buttons should be visible when minimizing and maximizing are
  122. // allowed.
  123. FrameCaptionButtonContainerView container1(CreateTestWidget(
  124. MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED, CLOSE_BUTTON_VISIBLE));
  125. InitContainer(&container1);
  126. container1.Layout();
  127. FrameCaptionButtonContainerView::TestApi t1(&container1);
  128. EXPECT_TRUE(t1.minimize_button()->GetVisible());
  129. EXPECT_TRUE(t1.size_button()->GetVisible());
  130. EXPECT_TRUE(t1.close_button()->GetVisible());
  131. EXPECT_TRUE(CheckButtonsAtEdges(&container1, *t1.minimize_button(),
  132. *t1.close_button()));
  133. // The minimize button should be visible when minimizing is allowed but
  134. // maximizing is disallowed.
  135. FrameCaptionButtonContainerView container2(CreateTestWidget(
  136. MAXIMIZE_DISALLOWED, MINIMIZE_ALLOWED, CLOSE_BUTTON_VISIBLE));
  137. InitContainer(&container2);
  138. container2.Layout();
  139. FrameCaptionButtonContainerView::TestApi t2(&container2);
  140. EXPECT_TRUE(t2.minimize_button()->GetVisible());
  141. EXPECT_FALSE(t2.size_button()->GetVisible());
  142. EXPECT_TRUE(t2.close_button()->GetVisible());
  143. EXPECT_TRUE(CheckButtonsAtEdges(&container2, *t2.minimize_button(),
  144. *t2.close_button()));
  145. // Neither the minimize button nor the size button should be visible when
  146. // neither minimizing nor maximizing are allowed.
  147. FrameCaptionButtonContainerView container3(CreateTestWidget(
  148. MAXIMIZE_DISALLOWED, MINIMIZE_DISALLOWED, CLOSE_BUTTON_VISIBLE));
  149. InitContainer(&container3);
  150. container3.Layout();
  151. FrameCaptionButtonContainerView::TestApi t3(&container3);
  152. EXPECT_FALSE(t3.minimize_button()->GetVisible());
  153. EXPECT_FALSE(t3.size_button()->GetVisible());
  154. EXPECT_TRUE(t3.close_button()->GetVisible());
  155. EXPECT_TRUE(
  156. CheckButtonsAtEdges(&container3, *t3.close_button(), *t3.close_button()));
  157. }
  158. // Tests that the layout animations trigered by button visibility result in the
  159. // correct placement of the buttons.
  160. TEST_F(FrameCaptionButtonContainerViewTest,
  161. TestUpdateSizeButtonVisibilityAnimation) {
  162. FrameCaptionButtonContainerView container(CreateTestWidget(
  163. MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED, CLOSE_BUTTON_VISIBLE));
  164. // Add an extra button to the left of the size button to verify that it is
  165. // repositioned similarly to the minimize button. This simulates the PWA menu
  166. // button being added to the left of the minimize button.
  167. views::View* extra_button = new views::StaticSizedView(gfx::Size(32, 32));
  168. container.AddChildViewAt(extra_button, 0);
  169. InitContainer(&container);
  170. container.Layout();
  171. FrameCaptionButtonContainerView::TestApi test(&container);
  172. gfx::Rect initial_extra_button_bounds = extra_button->bounds();
  173. gfx::Rect initial_minimize_button_bounds = test.minimize_button()->bounds();
  174. gfx::Rect initial_size_button_bounds = test.size_button()->bounds();
  175. gfx::Rect initial_close_button_bounds = test.close_button()->bounds();
  176. gfx::Rect initial_container_bounds = container.bounds();
  177. ASSERT_EQ(initial_minimize_button_bounds.x(),
  178. initial_extra_button_bounds.right());
  179. ASSERT_EQ(initial_size_button_bounds.x(),
  180. initial_minimize_button_bounds.right());
  181. ASSERT_EQ(initial_close_button_bounds.x(),
  182. initial_size_button_bounds.right());
  183. // Button positions should be the same when entering tablet mode.
  184. Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
  185. container.UpdateCaptionButtonState(false /*=animate*/);
  186. test.EndAnimations();
  187. // Parent needs to layout in response to size change.
  188. container.Layout();
  189. EXPECT_TRUE(test.minimize_button()->GetVisible());
  190. EXPECT_TRUE(test.size_button()->GetVisible());
  191. EXPECT_TRUE(test.close_button()->GetVisible());
  192. gfx::Rect extra_button_bounds = extra_button->bounds();
  193. gfx::Rect minimize_button_bounds = test.minimize_button()->bounds();
  194. gfx::Rect size_button_bounds = test.size_button()->bounds();
  195. gfx::Rect close_button_bounds = test.close_button()->bounds();
  196. EXPECT_EQ(minimize_button_bounds.x(), extra_button_bounds.right());
  197. EXPECT_EQ(size_button_bounds.x(), minimize_button_bounds.right());
  198. EXPECT_EQ(close_button_bounds.x(), size_button_bounds.right());
  199. EXPECT_EQ(initial_size_button_bounds, test.size_button()->bounds());
  200. EXPECT_EQ(initial_close_button_bounds.size(), close_button_bounds.size());
  201. EXPECT_EQ(container.GetPreferredSize().width(),
  202. initial_container_bounds.width());
  203. // Button positions should be the same when leaving tablet mode.
  204. Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
  205. container.UpdateCaptionButtonState(false /*=animate*/);
  206. // Calling code needs to layout in response to size change.
  207. container.Layout();
  208. test.EndAnimations();
  209. EXPECT_TRUE(test.minimize_button()->GetVisible());
  210. EXPECT_TRUE(test.size_button()->GetVisible());
  211. EXPECT_TRUE(test.close_button()->GetVisible());
  212. EXPECT_EQ(initial_extra_button_bounds, extra_button->bounds());
  213. EXPECT_EQ(initial_minimize_button_bounds, test.minimize_button()->bounds());
  214. EXPECT_EQ(initial_size_button_bounds, test.size_button()->bounds());
  215. EXPECT_EQ(initial_close_button_bounds, test.close_button()->bounds());
  216. EXPECT_EQ(container.GetPreferredSize().width(),
  217. initial_container_bounds.width());
  218. }
  219. // Test that the close button is visible when
  220. // |ShouldShowCloseButton()| returns true.
  221. TEST_F(FrameCaptionButtonContainerViewTest, ShouldShowCloseButtonTrue) {
  222. FrameCaptionButtonContainerView container(CreateTestWidget(
  223. MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED, CLOSE_BUTTON_VISIBLE));
  224. InitContainer(&container);
  225. container.Layout();
  226. FrameCaptionButtonContainerView::TestApi testApi(&container);
  227. EXPECT_TRUE(testApi.close_button()->GetVisible());
  228. EXPECT_TRUE(testApi.close_button()->GetEnabled());
  229. }
  230. // Test that the close button is not visible when
  231. // |ShouldShowCloseButton()| returns false.
  232. TEST_F(FrameCaptionButtonContainerViewTest, ShouldShowCloseButtonFalse) {
  233. FrameCaptionButtonContainerView container(CreateTestWidget(
  234. MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED, CLOSE_BUTTON_NOT_VISIBLE));
  235. InitContainer(&container);
  236. container.Layout();
  237. FrameCaptionButtonContainerView::TestApi testApi(&container);
  238. EXPECT_FALSE(testApi.close_button()->GetVisible());
  239. EXPECT_TRUE(testApi.close_button()->GetEnabled());
  240. }
  241. // Test that overriding size button behavior works properly.
  242. TEST_F(FrameCaptionButtonContainerViewTest, TestSizeButtonBehaviorOverride) {
  243. auto* widget = CreateTestWidget(MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED,
  244. CLOSE_BUTTON_VISIBLE);
  245. widget->Show();
  246. auto* window_state = WindowState::Get(widget->GetNativeWindow());
  247. FrameCaptionButtonContainerView container(widget);
  248. InitContainer(&container);
  249. widget->GetContentsView()->AddChildView(&container);
  250. container.Layout();
  251. FrameCaptionButtonContainerView::TestApi testApi(&container);
  252. EXPECT_TRUE(window_state->IsNormalStateType());
  253. // Test that the size button works without override.
  254. ClickSizeButton(&testApi);
  255. EXPECT_TRUE(window_state->IsMaximized());
  256. ClickSizeButton(&testApi);
  257. EXPECT_TRUE(window_state->IsNormalStateType());
  258. // Test that the size button behavior is overridden when override callback
  259. // returning true is set.
  260. bool called = false;
  261. container.SetOnSizeButtonPressedCallback(
  262. base::BindLambdaForTesting([&called]() {
  263. called = true;
  264. return true;
  265. }));
  266. ClickSizeButton(&testApi);
  267. EXPECT_TRUE(window_state->IsNormalStateType());
  268. EXPECT_TRUE(called);
  269. // Test that the override callback is removable.
  270. called = false;
  271. container.ClearOnSizeButtonPressedCallback();
  272. ClickSizeButton(&testApi);
  273. EXPECT_TRUE(window_state->IsMaximized());
  274. EXPECT_FALSE(called);
  275. ClickSizeButton(&testApi);
  276. EXPECT_TRUE(window_state->IsNormalStateType());
  277. EXPECT_FALSE(called);
  278. // Test that the size button behavior fall back to the default one when
  279. // override callback returns false.
  280. called = false;
  281. container.SetOnSizeButtonPressedCallback(
  282. base::BindLambdaForTesting([&called]() {
  283. called = true;
  284. return false;
  285. }));
  286. ClickSizeButton(&testApi);
  287. EXPECT_TRUE(window_state->IsMaximized());
  288. EXPECT_TRUE(called);
  289. ClickSizeButton(&testApi);
  290. EXPECT_TRUE(window_state->IsNormalStateType());
  291. EXPECT_TRUE(called);
  292. }
  293. TEST_F(FrameCaptionButtonContainerViewTest, ResizeButtonRestoreBehavior) {
  294. auto* widget = CreateTestWidget(MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED,
  295. CLOSE_BUTTON_VISIBLE);
  296. widget->Show();
  297. auto* window_state = WindowState::Get(widget->GetNativeWindow());
  298. FrameCaptionButtonContainerView container(widget);
  299. InitContainer(&container);
  300. widget->GetContentsView()->AddChildView(&container);
  301. container.Layout();
  302. FrameCaptionButtonContainerView::TestApi testApi(&container);
  303. // Test using size button to restore the maximized window to its normal window
  304. // state.
  305. ClickSizeButton(&testApi);
  306. EXPECT_TRUE(window_state->IsMaximized());
  307. ClickSizeButton(&testApi);
  308. EXPECT_TRUE(window_state->IsNormalStateType());
  309. // Snap the window.
  310. const WindowSnapWMEvent snap_event(WM_EVENT_SNAP_PRIMARY);
  311. window_state->OnWMEvent(&snap_event);
  312. // Check the window is now snapped.
  313. EXPECT_TRUE(window_state->IsSnapped());
  314. ClickSizeButton(&testApi);
  315. EXPECT_TRUE(window_state->IsMaximized());
  316. ClickSizeButton(&testApi);
  317. // Check instead of returning back to normal window state, the window should
  318. // return back to Snapped window state.
  319. EXPECT_TRUE(window_state->IsSnapped());
  320. }
  321. // Test float button behavior.
  322. TEST_F(WindowFloatButtonTest, TestFloatButtonBehavior) {
  323. auto* widget = CreateTestWidget(MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED,
  324. CLOSE_BUTTON_VISIBLE);
  325. widget->Show();
  326. FrameCaptionButtonContainerView container(widget);
  327. InitContainer(&container);
  328. widget->GetContentsView()->AddChildView(&container);
  329. container.Layout();
  330. FrameCaptionButtonContainerView::TestApi testApi(&container);
  331. ClickFloatButton(&testApi);
  332. auto* window_state = WindowState::Get(widget->GetNativeWindow());
  333. // Check if window is floated.
  334. auto* window = widget->GetNativeWindow();
  335. EXPECT_TRUE(window_state->IsFloated());
  336. EXPECT_EQ(window->GetProperty(chromeos::kWindowStateTypeKey),
  337. chromeos::WindowStateType::kFloated);
  338. ClickFloatButton(&testApi);
  339. // Check if window is unfloated.
  340. EXPECT_TRUE(window_state->IsNormalStateType());
  341. EXPECT_FALSE(window_state->IsFloated());
  342. EXPECT_EQ(window->GetProperty(chromeos::kWindowStateTypeKey),
  343. chromeos::WindowStateType::kNormal);
  344. }
  345. } // namespace ash