bubble_slide_animator_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. // Copyright 2021 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 "ui/views/animation/bubble_slide_animator.h"
  5. #include <memory>
  6. #include "base/memory/raw_ptr.h"
  7. #include "base/test/bind.h"
  8. #include "base/time/time.h"
  9. #include "ui/base/ui_base_types.h"
  10. #include "ui/gfx/animation/animation_test_api.h"
  11. #include "ui/views/bubble/bubble_dialog_delegate_view.h"
  12. #include "ui/views/layout/fill_layout.h"
  13. #include "ui/views/layout/flex_layout_view.h"
  14. #include "ui/views/test/widget_test.h"
  15. #include "ui/views/view.h"
  16. #include "ui/views/widget/widget.h"
  17. namespace views {
  18. namespace {
  19. constexpr base::TimeDelta kSlideDuration = base::Milliseconds(1000);
  20. constexpr base::TimeDelta kHalfSlideDuration = kSlideDuration / 2;
  21. // This will be the size of the three horizontally-oriented anchor views as well
  22. // as the target size for the floating view.
  23. constexpr gfx::Size kTestViewSize(100, 100);
  24. // Make this big enough that even if we anchor to a third view horizontally, no
  25. // mirroring should happen.
  26. constexpr gfx::Rect kAnchorWidgetRect(50, 50, 400, 250);
  27. class TestBubbleView : public BubbleDialogDelegateView {
  28. public:
  29. explicit TestBubbleView(View* anchor_view)
  30. : BubbleDialogDelegateView(anchor_view, BubbleBorder::TOP_LEFT) {
  31. SetButtons(ui::DIALOG_BUTTON_NONE);
  32. SetLayoutManager(std::make_unique<FillLayout>());
  33. AddChildView(std::make_unique<View>())->SetPreferredSize(kTestViewSize);
  34. }
  35. protected:
  36. void AddedToWidget() override {
  37. BubbleDialogDelegateView::AddedToWidget();
  38. SizeToContents();
  39. }
  40. };
  41. class TestBubbleSlideAnimator : public BubbleSlideAnimator {
  42. public:
  43. using BubbleSlideAnimator::BubbleSlideAnimator;
  44. ~TestBubbleSlideAnimator() override = default;
  45. void AnimationContainerWasSet(gfx::AnimationContainer* container) override {
  46. BubbleSlideAnimator::AnimationContainerWasSet(container);
  47. container_test_api_.reset();
  48. if (container) {
  49. container_test_api_ =
  50. std::make_unique<gfx::AnimationContainerTestApi>(container);
  51. }
  52. }
  53. gfx::AnimationContainerTestApi* test_api() {
  54. return container_test_api_.get();
  55. }
  56. private:
  57. std::unique_ptr<gfx::AnimationContainerTestApi> container_test_api_;
  58. };
  59. } // namespace
  60. class BubbleSlideAnimatorTest : public test::WidgetTest {
  61. public:
  62. void SetUp() override {
  63. test::WidgetTest::SetUp();
  64. anchor_widget_ = CreateTestWidget(Widget::InitParams::Type::TYPE_WINDOW);
  65. auto* const contents_view = anchor_widget_->GetRootView()->AddChildView(
  66. std::make_unique<FlexLayoutView>());
  67. contents_view->SetOrientation(LayoutOrientation::kHorizontal);
  68. contents_view->SetMainAxisAlignment(LayoutAlignment::kStart);
  69. contents_view->SetCrossAxisAlignment(LayoutAlignment::kStart);
  70. view1_ = contents_view->AddChildView(std::make_unique<View>());
  71. view2_ = contents_view->AddChildView(std::make_unique<View>());
  72. view3_ = contents_view->AddChildView(std::make_unique<View>());
  73. view1_->SetPreferredSize(kTestViewSize);
  74. view2_->SetPreferredSize(kTestViewSize);
  75. view3_->SetPreferredSize(kTestViewSize);
  76. anchor_widget_->Show();
  77. anchor_widget_->SetBounds(kAnchorWidgetRect);
  78. bubble_ = new TestBubbleView(view1_);
  79. widget_ = BubbleDialogDelegateView::CreateBubble(bubble_);
  80. delegate_ = std::make_unique<TestBubbleSlideAnimator>(bubble_);
  81. delegate_->SetSlideDuration(kSlideDuration);
  82. }
  83. void TearDown() override {
  84. CloseWidget();
  85. if (anchor_widget_ && !anchor_widget_->IsClosed())
  86. anchor_widget_->CloseNow();
  87. test::WidgetTest::TearDown();
  88. }
  89. void CloseWidget() {
  90. if (widget_ && !widget_->IsClosed())
  91. widget_->CloseNow();
  92. widget_ = nullptr;
  93. bubble_ = nullptr;
  94. }
  95. protected:
  96. std::unique_ptr<Widget> anchor_widget_;
  97. raw_ptr<BubbleDialogDelegateView> bubble_ = nullptr;
  98. raw_ptr<Widget> widget_ = nullptr;
  99. raw_ptr<View> view1_;
  100. raw_ptr<View> view2_;
  101. raw_ptr<View> view3_;
  102. std::unique_ptr<TestBubbleSlideAnimator> delegate_;
  103. };
  104. TEST_F(BubbleSlideAnimatorTest, InitiateSlide) {
  105. const auto bounds = widget_->GetWindowBoundsInScreen();
  106. delegate_->AnimateToAnchorView(view2_);
  107. // Shouldn't animate from here yet.
  108. EXPECT_EQ(bounds, widget_->GetWindowBoundsInScreen());
  109. EXPECT_TRUE(delegate_->is_animating());
  110. }
  111. TEST_F(BubbleSlideAnimatorTest, SlideProgresses) {
  112. const auto starting_bounds = widget_->GetWindowBoundsInScreen();
  113. delegate_->AnimateToAnchorView(view2_);
  114. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  115. const auto intermediate_bounds = widget_->GetWindowBoundsInScreen();
  116. EXPECT_TRUE(delegate_->is_animating());
  117. EXPECT_EQ(intermediate_bounds.y(), starting_bounds.y());
  118. EXPECT_GT(intermediate_bounds.x(), starting_bounds.x());
  119. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  120. const auto final_bounds = widget_->GetWindowBoundsInScreen();
  121. EXPECT_FALSE(delegate_->is_animating());
  122. EXPECT_EQ(final_bounds.y(), starting_bounds.y());
  123. EXPECT_GT(final_bounds.x(), intermediate_bounds.x());
  124. EXPECT_EQ(final_bounds.x(), starting_bounds.x() + view2_->x() - view1_->x());
  125. }
  126. TEST_F(BubbleSlideAnimatorTest, SnapToAnchorView) {
  127. const auto starting_bounds = widget_->GetWindowBoundsInScreen();
  128. delegate_->SnapToAnchorView(view2_);
  129. const auto final_bounds = widget_->GetWindowBoundsInScreen();
  130. EXPECT_FALSE(delegate_->is_animating());
  131. EXPECT_EQ(final_bounds.y(), starting_bounds.y());
  132. EXPECT_EQ(final_bounds.x(), starting_bounds.x() + view2_->x() - view1_->x());
  133. }
  134. TEST_F(BubbleSlideAnimatorTest, SlideCallbacksCalled) {
  135. int progress_count = 0;
  136. int complete_count = 0;
  137. double last_progress = 0.0;
  138. auto progress_sub = delegate_->AddSlideProgressedCallback(
  139. base::BindLambdaForTesting([&](BubbleSlideAnimator*, double progress) {
  140. last_progress = progress;
  141. ++progress_count;
  142. }));
  143. auto completed_sub =
  144. delegate_->AddSlideCompleteCallback(base::BindLambdaForTesting(
  145. [&](BubbleSlideAnimator*) { ++complete_count; }));
  146. delegate_->AnimateToAnchorView(view2_);
  147. EXPECT_EQ(0, progress_count);
  148. EXPECT_EQ(0, complete_count);
  149. EXPECT_EQ(0.0, last_progress);
  150. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  151. EXPECT_EQ(1, progress_count);
  152. EXPECT_EQ(0, complete_count);
  153. EXPECT_GT(last_progress, 0.0);
  154. EXPECT_LT(last_progress, 1.0);
  155. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  156. EXPECT_EQ(2, progress_count);
  157. EXPECT_EQ(1, complete_count);
  158. EXPECT_EQ(1.0, last_progress);
  159. }
  160. TEST_F(BubbleSlideAnimatorTest, SnapCallbacksCalled) {
  161. int progress_count = 0;
  162. int complete_count = 0;
  163. double last_progress = 0.0;
  164. auto progress_sub = delegate_->AddSlideProgressedCallback(
  165. base::BindLambdaForTesting([&](BubbleSlideAnimator*, double progress) {
  166. last_progress = progress;
  167. ++progress_count;
  168. }));
  169. auto completed_sub =
  170. delegate_->AddSlideCompleteCallback(base::BindLambdaForTesting(
  171. [&](BubbleSlideAnimator*) { ++complete_count; }));
  172. delegate_->SnapToAnchorView(view2_);
  173. EXPECT_EQ(1, progress_count);
  174. EXPECT_EQ(1, complete_count);
  175. EXPECT_EQ(1.0, last_progress);
  176. }
  177. TEST_F(BubbleSlideAnimatorTest, InterruptingWithSlideCallsCorrectCallbacks) {
  178. int progress_count = 0;
  179. int complete_count = 0;
  180. double last_progress = 0.0;
  181. auto progress_sub = delegate_->AddSlideProgressedCallback(
  182. base::BindLambdaForTesting([&](BubbleSlideAnimator*, double progress) {
  183. last_progress = progress;
  184. ++progress_count;
  185. }));
  186. auto completed_sub =
  187. delegate_->AddSlideCompleteCallback(base::BindLambdaForTesting(
  188. [&](BubbleSlideAnimator*) { ++complete_count; }));
  189. delegate_->AnimateToAnchorView(view2_);
  190. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  191. EXPECT_EQ(1, progress_count);
  192. EXPECT_EQ(0, complete_count);
  193. delegate_->AnimateToAnchorView(view3_);
  194. EXPECT_EQ(1, progress_count);
  195. EXPECT_EQ(0, complete_count);
  196. delegate_->test_api()->IncrementTime(kSlideDuration);
  197. EXPECT_EQ(2, progress_count);
  198. EXPECT_EQ(1, complete_count);
  199. }
  200. TEST_F(BubbleSlideAnimatorTest, InterruptingWithSnapCallsCorrectCallbacks) {
  201. int progress_count = 0;
  202. int complete_count = 0;
  203. double last_progress = 0.0;
  204. auto progress_sub = delegate_->AddSlideProgressedCallback(
  205. base::BindLambdaForTesting([&](BubbleSlideAnimator*, double progress) {
  206. last_progress = progress;
  207. ++progress_count;
  208. }));
  209. auto completed_sub =
  210. delegate_->AddSlideCompleteCallback(base::BindLambdaForTesting(
  211. [&](BubbleSlideAnimator*) { ++complete_count; }));
  212. delegate_->AnimateToAnchorView(view2_);
  213. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  214. EXPECT_EQ(1, progress_count);
  215. EXPECT_EQ(0, complete_count);
  216. delegate_->SnapToAnchorView(view3_);
  217. EXPECT_EQ(2, progress_count);
  218. EXPECT_EQ(1, complete_count);
  219. EXPECT_EQ(1.0, last_progress);
  220. }
  221. TEST_F(BubbleSlideAnimatorTest, CancelAnimation) {
  222. int progress_count = 0;
  223. int complete_count = 0;
  224. double last_progress = 0.0;
  225. auto progress_sub = delegate_->AddSlideProgressedCallback(
  226. base::BindLambdaForTesting([&](BubbleSlideAnimator*, double progress) {
  227. last_progress = progress;
  228. ++progress_count;
  229. }));
  230. auto completed_sub =
  231. delegate_->AddSlideCompleteCallback(base::BindLambdaForTesting(
  232. [&](BubbleSlideAnimator*) { ++complete_count; }));
  233. const auto initial_bounds = widget_->GetWindowBoundsInScreen();
  234. delegate_->AnimateToAnchorView(view2_);
  235. delegate_->test_api()->IncrementTime(kSlideDuration);
  236. const auto second_bounds = widget_->GetWindowBoundsInScreen();
  237. delegate_->AnimateToAnchorView(view1_);
  238. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  239. const auto final_bounds = widget_->GetWindowBoundsInScreen();
  240. delegate_->StopAnimation();
  241. EXPECT_FALSE(delegate_->is_animating());
  242. EXPECT_EQ(2, progress_count);
  243. EXPECT_EQ(1, complete_count);
  244. EXPECT_GT(last_progress, 0.0);
  245. EXPECT_LT(last_progress, 1.0);
  246. EXPECT_GT(final_bounds.x(), initial_bounds.x());
  247. EXPECT_LT(final_bounds.x(), second_bounds.x());
  248. }
  249. TEST_F(BubbleSlideAnimatorTest, MultipleSlidesInSequence) {
  250. // First slide.
  251. delegate_->AnimateToAnchorView(view2_);
  252. delegate_->test_api()->IncrementTime(kSlideDuration);
  253. const auto first_bounds = widget_->GetWindowBoundsInScreen();
  254. EXPECT_FALSE(delegate_->is_animating());
  255. // Second slide.
  256. delegate_->AnimateToAnchorView(view3_);
  257. EXPECT_TRUE(delegate_->is_animating());
  258. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  259. // Ensure we are sliding.
  260. const auto intermediate_bounds = widget_->GetWindowBoundsInScreen();
  261. EXPECT_TRUE(delegate_->is_animating());
  262. EXPECT_EQ(intermediate_bounds.y(), first_bounds.y());
  263. EXPECT_GT(intermediate_bounds.x(), first_bounds.x());
  264. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  265. // Ensure we're done.
  266. const auto final_bounds = widget_->GetWindowBoundsInScreen();
  267. EXPECT_FALSE(delegate_->is_animating());
  268. EXPECT_EQ(final_bounds.y(), first_bounds.y());
  269. EXPECT_EQ(final_bounds.x(), first_bounds.x() + view3_->x() - view2_->x());
  270. }
  271. TEST_F(BubbleSlideAnimatorTest, SlideBackToStartingPosition) {
  272. const auto first_bounds = widget_->GetWindowBoundsInScreen();
  273. delegate_->AnimateToAnchorView(view3_);
  274. delegate_->test_api()->IncrementTime(kSlideDuration);
  275. delegate_->AnimateToAnchorView(view1_);
  276. delegate_->test_api()->IncrementTime(kSlideDuration);
  277. const auto final_bounds = widget_->GetWindowBoundsInScreen();
  278. EXPECT_FALSE(delegate_->is_animating());
  279. EXPECT_EQ(final_bounds, first_bounds);
  280. }
  281. TEST_F(BubbleSlideAnimatorTest, InterruptingSlide) {
  282. const auto starting_bounds = widget_->GetWindowBoundsInScreen();
  283. // Start the first slide.
  284. delegate_->AnimateToAnchorView(view2_);
  285. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  286. const auto intermediate_bounds1 = widget_->GetWindowBoundsInScreen();
  287. EXPECT_TRUE(delegate_->is_animating());
  288. // Interrupt mid-slide with another slide.
  289. delegate_->AnimateToAnchorView(view3_);
  290. EXPECT_TRUE(delegate_->is_animating());
  291. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  292. // Ensure we are sliding.
  293. const auto intermediate_bounds2 = widget_->GetWindowBoundsInScreen();
  294. EXPECT_TRUE(delegate_->is_animating());
  295. EXPECT_EQ(intermediate_bounds2.y(), intermediate_bounds1.y());
  296. EXPECT_GT(intermediate_bounds2.x(), intermediate_bounds1.x());
  297. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  298. // Ensure we are done.
  299. const auto final_bounds = widget_->GetWindowBoundsInScreen();
  300. EXPECT_FALSE(delegate_->is_animating());
  301. EXPECT_EQ(final_bounds.y(), starting_bounds.y());
  302. EXPECT_EQ(final_bounds.x(), starting_bounds.x() + view3_->x() - view1_->x());
  303. }
  304. TEST_F(BubbleSlideAnimatorTest, WidgetClosedDuringSlide) {
  305. delegate_->AnimateToAnchorView(view2_);
  306. CloseWidget();
  307. EXPECT_FALSE(delegate_->is_animating());
  308. }
  309. TEST_F(BubbleSlideAnimatorTest, AnimatorDestroyedDuringSlide) {
  310. delegate_->AnimateToAnchorView(view2_);
  311. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  312. delegate_.reset();
  313. }
  314. TEST_F(BubbleSlideAnimatorTest, AnimationSetsAnchorView) {
  315. delegate_->AnimateToAnchorView(view2_);
  316. delegate_->test_api()->IncrementTime(kSlideDuration);
  317. EXPECT_EQ(view2_, bubble_->GetAnchorView());
  318. delegate_->AnimateToAnchorView(view3_);
  319. delegate_->test_api()->IncrementTime(kSlideDuration);
  320. EXPECT_EQ(view3_, bubble_->GetAnchorView());
  321. }
  322. TEST_F(BubbleSlideAnimatorTest, SnapSetsAnchorView) {
  323. delegate_->SnapToAnchorView(view2_);
  324. EXPECT_EQ(view2_, bubble_->GetAnchorView());
  325. delegate_->SnapToAnchorView(view3_);
  326. EXPECT_EQ(view3_, bubble_->GetAnchorView());
  327. }
  328. TEST_F(BubbleSlideAnimatorTest, CancelDoesntSetAnchorView) {
  329. delegate_->AnimateToAnchorView(view2_);
  330. delegate_->test_api()->IncrementTime(kHalfSlideDuration);
  331. delegate_->StopAnimation();
  332. EXPECT_EQ(view1_, bubble_->GetAnchorView());
  333. }
  334. } // namespace views