chromium/ash/system/status_area_animation_controller_unittest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/status_area_animation_controller.h"

#include "ash/ime/ime_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/notification_center/notification_center_test_api.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/unified/notification_counter_view.h"
#include "ash/system/unified/notification_icons_controller.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/display/manager/display_manager.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/message_center/message_center.h"

namespace ash {

class TrayItemViewAnimationWaiter {
 public:
  explicit TrayItemViewAnimationWaiter(TrayItemView* tray_item)
      : tray_item_(tray_item) {}
  TrayItemViewAnimationWaiter(const TrayItemViewAnimationWaiter&) = delete;
  TrayItemViewAnimationWaiter& operator=(const TrayItemViewAnimationWaiter&) =
      delete;
  ~TrayItemViewAnimationWaiter() = default;

  // Waits for `tray_item_`'s visibility animation to finish, or no-op if it is
  // not currently animating.
  void Wait() {
    if (tray_item_->IsAnimating()) {
      tray_item_->SetAnimationIdleClosureForTest(base::BindOnce(
          &TrayItemViewAnimationWaiter::OnTrayItemAnimationFinished,
          weak_ptr_factory_.GetWeakPtr()));
      run_loop_.Run();
    }
  }

 private:
  // Called when `tray_item_`'s visibility animation finishes.
  void OnTrayItemAnimationFinished() { run_loop_.Quit(); }

  // The tray item whose animation is being waited for.
  raw_ptr<TrayItemView> tray_item_ = nullptr;

  base::RunLoop run_loop_;

  base::WeakPtrFactory<TrayItemViewAnimationWaiter> weak_ptr_factory_{this};
};

class StatusAreaAnimationControllerTest : public AshTestBase {
 public:
  StatusAreaAnimationControllerTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
  }
  StatusAreaAnimationControllerTest(const StatusAreaAnimationControllerTest&) =
      delete;
  StatusAreaAnimationControllerTest& operator=(
      const StatusAreaAnimationControllerTest&) = delete;
  ~StatusAreaAnimationControllerTest() override = default;

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();
    test_api = std::make_unique<NotificationCenterTestApi>();
    // Tray visibility animations may still be disabled due to changes in
    // session state not fully propagating. Running all pending tasks guarantees
    // that the necessary scoped closure runners are executed, thus ensuring
    // that visibility animations are enabled before proceeding with the rest of
    // the test.
    base::RunLoop().RunUntilIdle();
    ASSERT_TRUE(test_api->GetTray()->IsShowAnimationEnabled());
  }

  bool IsNotificationCounterAnimationRunning() {
    return notification_counter()->IsAnimating();
  }

  bool IsCapsLockTrayItemAnimationRunning() {
    return caps_lock_tray_item()->IsAnimating();
  }

  NotificationCounterView* notification_counter() {
    return test_api->GetTray()
        ->notification_icons_controller()
        ->notification_counter_view();
  }

  NotificationIconTrayItemView* caps_lock_tray_item() {
    // The last tray item will be used to show the caps lock notification icon.
    return test_api->GetTray()
        ->notification_icons_controller()
        ->tray_items()
        .back();
  }

  std::unique_ptr<NotificationCenterTestApi> test_api;
};

// Tests that the notification center tray's `TrayItemView`s' animations are
// disabled while the notification center tray is running its initial show
// animation.
TEST_F(StatusAreaAnimationControllerTest,
       TrayItemAnimationDisabledDuringShowAnimation) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());
  ASSERT_FALSE(notification_counter()->GetVisible());

  // Add a notification to make the notification center tray visible.
  test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());
  ASSERT_TRUE(test_api->IsTrayShown());

  // Verify that the notification counter does not start animating.
  EXPECT_FALSE(IsNotificationCounterAnimationRunning());

  // Wait for the tray's show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the notification counter is visible.
  EXPECT_TRUE(notification_counter()->GetVisible());
  EXPECT_EQ(notification_counter()->layer()->opacity(), 1);
}

// Tests that the notification center tray's `TrayItemView`s' animations are
// disabled while the notification center tray is running its initial show
// animation on a secondary display.
TEST_F(StatusAreaAnimationControllerTest,
       TrayItemAnimationDisabledDuringShowAnimationOnSecondaryDisplay) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Create two displays.
  UpdateDisplay("800x799,800x799");
  auto secondary_display_id = display_manager()->GetDisplayAt(1).id();

  // Add a notification to make the notification center tray visible on both
  // displays.
  test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayShown());
  ASSERT_TRUE(test_api->IsTrayShownOnDisplay(secondary_display_id));
  ASSERT_TRUE(test_api->IsTrayAnimating());
  ASSERT_TRUE(test_api->IsTrayAnimatingOnDisplay(secondary_display_id));

  // Verify that the notification counter on the secondary display does not
  // start animating.
  EXPECT_FALSE(
      test_api->IsNotificationCounterAnimatingOnDisplay(secondary_display_id));

  // Wait for the secondary display's tray show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(
      test_api->GetTrayOnDisplay(secondary_display_id)->layer());
  ASSERT_FALSE(test_api->IsTrayAnimatingOnDisplay(secondary_display_id));

  // Verify that the secondary display's notification counter is visible.
  EXPECT_TRUE(
      test_api->IsNotificationCounterShownOnDisplay(secondary_display_id));
  EXPECT_EQ(test_api->GetNotificationCounterOnDisplay(secondary_display_id)
                ->layer()
                ->opacity(),
            1);
}

// Tests that the notification center tray's `TrayItemView`s' animations are
// disabled while the notification center tray is running its initial hide
// animation.
TEST_F(StatusAreaAnimationControllerTest,
       TrayItemAnimationDisabledDuringHideAnimation) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());
  ASSERT_FALSE(notification_counter()->GetVisible());

  // Add a notification to make the notification center tray visible.
  auto id = test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());
  ASSERT_TRUE(test_api->IsTrayShown());

  // Wait for the notification center tray's show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the notification center tray is visible.
  ASSERT_TRUE(test_api->IsTrayShown());

  // Remove the notification to cause the notification center tray to start its
  // hide animation.
  test_api->RemoveNotification(id);

  // Verify that the notification center tray is running its hide animation.
  ASSERT_TRUE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
  ASSERT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);

  // Verify that the notification counter does not start animating.
  EXPECT_FALSE(IsNotificationCounterAnimationRunning());

  // Wait for the tray's hide animation to finish.
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the notification counter is not visible.
  EXPECT_FALSE(notification_counter()->GetVisible());
  EXPECT_EQ(notification_counter()->layer()->opacity(), 0);
}

// Tests that `NotificationIconTrayItemView`s are not reset until the end of the
// `NotificationCenterTray`'s hide animation. This was added for b/284991444.
TEST_F(StatusAreaAnimationControllerTest,
       NotificationIconNotResetWhileNotificationTrayHideAnimationRunning) {
  // Note that animations still finish immediately at this stage of the test.

  // Add a critical warning system notification.
  auto id = test_api->AddCriticalWarningSystemNotification();
  auto* notification_icon = test_api->GetNotificationIconForId(id);
  CHECK(notification_icon);
  auto icon_image = notification_icon->image_view()->GetImage();

  // Verify that the notification icon is showing in the notification tray, and
  // that the tray is finished animating.
  ASSERT_TRUE(test_api->IsNotificationIconShown());
  ASSERT_TRUE(test_api->IsTrayShown());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Set the animation duration scale to some small non-zero value for the rest
  // of the test.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Remove the notification to start the notification tray's hide animation.
  test_api->RemoveNotification(id);
  ASSERT_TRUE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
  ASSERT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);

  // Verify that the notification icon has not been reset yet.
  EXPECT_TRUE(icon_image.BackedBySameObjectAs(
      notification_icon->image_view()->GetImage()));

  // Wait for the notification tray to finish its hide animation.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the notification icon has been reset.
  EXPECT_TRUE(notification_icon->image_view()->GetImage().BackedBySameObjectAs(
      gfx::ImageSkia()));
}

// Tests that `NotificationIconTrayItemView`s are not reset until the end of
// their hide animation. This was added for b/284991444.
TEST_F(StatusAreaAnimationControllerTest,
       NotificationIconNotResetWhileHideAnimationRunning) {
  // Note that animations still finish immediately at this stage of the test.

  // Add a standard notification as well as a critical warning system
  // notification.
  test_api->AddNotification();
  auto id = test_api->AddCriticalWarningSystemNotification();
  auto* notification_icon = test_api->GetNotificationIconForId(id);
  CHECK(notification_icon);
  auto icon_image = notification_icon->image_view()->GetImage();

  // Verify that both the notification counter and a notification icon are
  // showing in the notification tray, and that the tray is finished animating.
  ASSERT_TRUE(test_api->IsNotificationCounterShown());
  ASSERT_TRUE(test_api->IsNotificationIconShown());
  ASSERT_TRUE(test_api->IsTrayShown());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Set the animation duration scale to some small non-zero value for the
  // rest of the test.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Remove the critical warning system notification to start the notification
  // icon's hide animation (note that the notification tray should remain
  // visible due to the presence of the standard notification).
  test_api->RemoveNotification(id);
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsNotificationCounterAnimating());
  ASSERT_TRUE(notification_icon->IsAnimating());
  ASSERT_FALSE(notification_icon->target_visible_for_testing());

  // Verify that the notification icon has not been reset yet.
  EXPECT_TRUE(icon_image.BackedBySameObjectAs(
      notification_icon->image_view()->GetImage()));

  // End the notification icon's hide animation.
  auto* icon_animation = notification_icon->animation_for_testing();
  icon_animation->End();
  ASSERT_FALSE(notification_icon->IsAnimating());

  // Verify that the notification icon has been reset.
  EXPECT_TRUE(notification_icon->image_view()->GetImage().BackedBySameObjectAs(
      gfx::ImageSkia()));
}

// Tests that the notification center tray's `TrayItemView`s' animations are
// disabled while the notification center tray is running its show animation,
// even when it's not the initial show animation.
TEST_F(StatusAreaAnimationControllerTest,
       TrayItemAnimationDisabledDuringNonInitialShowAnimation) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());
  ASSERT_FALSE(notification_counter()->GetVisible());

  // Add a notification to make the notification center tray visible.
  auto id = test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the tray's show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Remove the notification, causing the tray to hide.
  test_api->RemoveNotification(id);
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the hide animation to finish.
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());
  ASSERT_FALSE(notification_counter()->GetVisible());

  // Add another notification to make the tray visible a second time.
  test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());
  ASSERT_TRUE(test_api->IsTrayShown());

  // Verify that the notification counter does not start animating.
  EXPECT_FALSE(IsNotificationCounterAnimationRunning());

  // Wait for the tray's show animation to finish.
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the notification counter is visible.
  EXPECT_TRUE(notification_counter()->GetVisible());
  EXPECT_EQ(notification_counter()->layer()->opacity(), 1);
}

// Tests that the notification center tray's `TrayItemView`s' animations are
// disabled while the notification center tray is running its hide animation,
// even when it's not the initial hide animation.
TEST_F(StatusAreaAnimationControllerTest,
       TrayItemAnimationDisabledDuringNonInitialHideAnimation) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());
  ASSERT_FALSE(notification_counter()->GetVisible());

  // Add a notification to make the notification center tray visible.
  auto id = test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the tray's show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_TRUE(test_api->IsTrayShown());
  ASSERT_TRUE(notification_counter()->GetVisible());

  // Remove the notification, causing the tray to hide.
  test_api->RemoveNotification(id);
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the hide animation to finish.
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());
  ASSERT_FALSE(notification_counter()->GetVisible());

  // Add another notification to make the tray visible a second time.
  id = test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the notification center tray's second show animation to finish.
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_TRUE(test_api->IsTrayShown());
  ASSERT_TRUE(notification_counter()->GetVisible());

  // Remove the notification, causing the notification center tray to start its
  // second hide animation.
  test_api->RemoveNotification(id);
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Verify that the notification counter does not start animating.
  EXPECT_FALSE(IsNotificationCounterAnimationRunning());

  // Wait for the notification center tray's hide animation to finish.
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());

  // Verify that the notification counter is hidden.
  EXPECT_FALSE(notification_counter()->GetVisible());
  EXPECT_EQ(notification_counter()->layer()->opacity(), 0);
}

// Tests that already-visible `TrayItemView`s do not animate when a new
// `TrayItemView` becomes visible.
TEST_F(StatusAreaAnimationControllerTest,
       VisibleTrayItemDoesNotAnimateDuringNewTrayItemAnimation) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());

  // Show the tray by adding a notification.
  test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the tray's show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_TRUE(notification_counter()->GetVisible());
  ASSERT_FALSE(IsNotificationCounterAnimationRunning());

  // Add a second icon to the tray (caps lock).
  Shell::Get()->ime_controller()->UpdateCapsLockState(true);

  // Verify that the new tray item (caps lock) animates in while the existing
  // tray item (counter) does not animate.
  EXPECT_TRUE(IsCapsLockTrayItemAnimationRunning());
  EXPECT_FALSE(IsNotificationCounterAnimationRunning());

  // Wait for the caps lock tray item show animation to finish.
  TrayItemViewAnimationWaiter(caps_lock_tray_item()).Wait();
  EXPECT_FALSE(IsCapsLockTrayItemAnimationRunning());

  // Verify that both tray items are visible.
  EXPECT_TRUE(caps_lock_tray_item()->GetVisible());
  EXPECT_EQ(caps_lock_tray_item()->layer()->opacity(), 1);
  EXPECT_TRUE(notification_counter()->GetVisible());
  EXPECT_EQ(notification_counter()->layer()->opacity(), 1);
}

// Tests that visible `TrayItemView`s can animate out without causing the entire
// notification center tray to animate when there are at least two items in the
// tray.
TEST_F(StatusAreaAnimationControllerTest,
       VisibleTrayItemAnimatesOutWithoutCausingWholeTrayAnimation) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_FALSE(test_api->IsTrayShown());

  // Show the tray by adding a notification.
  auto id = test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the tray's show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());
  ASSERT_TRUE(notification_counter()->GetVisible());

  // Add a second icon (caps lock) to the tray (this also adds a second
  // notification).
  Shell::Get()->ime_controller()->UpdateCapsLockState(true);

  // Wait for the caps lock tray item show animation to finish.
  TrayItemViewAnimationWaiter(caps_lock_tray_item()).Wait();
  ASSERT_FALSE(IsCapsLockTrayItemAnimationRunning());
  ASSERT_TRUE(caps_lock_tray_item()->GetVisible());

  // Remove the initial notification, causing the notification counter to hide.
  test_api->RemoveNotification(id);
  ASSERT_EQ(test_api->GetNotificationCount(), 1u);

  // Verify that the notification counter animates out while the tray remains
  // visible.
  EXPECT_TRUE(IsNotificationCounterAnimationRunning());
  EXPECT_TRUE(test_api->GetTray()->GetVisible());
  EXPECT_FALSE(test_api->IsTrayAnimating());

  // Wait for the notification counter hide animation to finish.
  TrayItemViewAnimationWaiter(notification_counter()).Wait();
  EXPECT_FALSE(IsNotificationCounterAnimationRunning());

  // Verify that the notification counter is no longer visible, but that the
  // tray is.
  EXPECT_FALSE(notification_counter()->GetVisible());
  EXPECT_TRUE(test_api->GetTray()->GetVisible());
}

// Tests that scheduling the show animation while the hide animation is running
// results in the `TrayBackgroundView` being visible once all animations are
// finished.
TEST_F(StatusAreaAnimationControllerTest, ShowWhileHideAnimationIsRunning) {
  // Show the tray by adding a notification. Note that animations still finish
  // immediately at this stage of the test.
  auto id = test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayShown());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Set the animation duration scale to some small non-zero value for the rest
  // of the test.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Remove the notification, causing the notification tray to hide.
  test_api->RemoveNotification(id);

  // Verify that the tray's hide animation is running.
  EXPECT_TRUE(test_api->IsTrayAnimating());
  EXPECT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
  EXPECT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);

  // Add another notification to show the tray again.
  test_api->AddNotification();

  // Verify that the tray is still animating, but this time it is the show
  // animation that is running.
  EXPECT_TRUE(test_api->IsTrayAnimating());
  EXPECT_TRUE(test_api->GetTray()->layer()->GetTargetVisibility());
  EXPECT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 1);

  // Wait for the show animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the tray is visible.
  EXPECT_TRUE(test_api->GetTray()->GetVisible());
  EXPECT_EQ(test_api->GetTray()->layer()->opacity(), 1);
}

// Tests that scheduling the hide animation while the show animation is running
// results in the `TrayBackgroundView` being hidden once all animations are
// finished.
TEST_F(StatusAreaAnimationControllerTest, HideWhileShowAnimationIsRunning) {
  ASSERT_FALSE(test_api->IsTrayShown());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Set the animation duration scale to some small non-zero value for the rest
  // of the test.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Cause the notification tray to start to show by adding a notification.
  auto id = test_api->AddNotification();

  // Verify that the notification tray's show animation is running.
  ASSERT_TRUE(test_api->IsTrayAnimating());
  ASSERT_TRUE(test_api->GetTray()->layer()->GetTargetVisibility());
  ASSERT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 1);

  // Remove the notification, causing the in-progress show animation to be
  // interrupted by the hide animation.
  test_api->RemoveNotification(id);

  // Verify that the tray is still animating, but this time it is the hide
  // animation that is running.
  EXPECT_TRUE(test_api->IsTrayAnimating());
  EXPECT_FALSE(test_api->GetTray()->layer()->GetTargetVisibility());
  EXPECT_EQ(test_api->GetTray()->layer()->GetTargetOpacity(), 0);

  // Wait for the hide animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the tray is hidden.
  EXPECT_FALSE(test_api->IsTrayShown());
  EXPECT_EQ(test_api->GetTray()->layer()->opacity(), 0);
}

// Tests that the notification center tray's tray items have their animations
// reset when the tray's hide animation ends.
TEST_F(StatusAreaAnimationControllerTest,
       HideAnimationEndedResetsTrayItemAnimation) {
  // Show the tray by adding a notification. Note that animations still finish
  // immediately at this stage of the test.
  auto id = test_api->AddNotification();
  ASSERT_TRUE(test_api->IsTrayShown());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Set the animation duration scale to some small non-zero value for the rest
  // of the test.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Toggle "Do not disturb" mode on and off to ensure that the notification
  // counter animates at least once (otherwise its animation object will still
  // be null and the rest of this test will crash).
  message_center::MessageCenter::Get()->SetQuietMode(true);
  message_center::MessageCenter::Get()->SetQuietMode(false);

  // Verify that the notification counter's animation is in its "shown" state.
  ASSERT_EQ(notification_counter()->animation_for_testing()->GetCurrentValue(),
            1);

  // Remove the notification to hide the notification center tray.
  test_api->RemoveNotification(id);
  ASSERT_TRUE(test_api->IsTrayAnimating());

  // Wait for the hide animation to finish.
  ui::LayerAnimationStoppedWaiter notification_center_tray_waiter;
  notification_center_tray_waiter.Wait(test_api->GetTray()->layer());
  ASSERT_FALSE(test_api->IsTrayAnimating());

  // Verify that the notification counter's animation is in its "hidden" state.
  EXPECT_EQ(notification_counter()->animation_for_testing()->GetCurrentValue(),
            0);
}

}  // namespace ash