chromium/ash/system/unified/quick_settings_footer_unittest.cc

// Copyright 2022 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/unified/quick_settings_footer.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/shell.h"
#include "ash/system/power/adaptive_charging_controller.h"
#include "ash/system/unified/power_button.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/user_manager/user_type.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"

namespace ash {

// Tests for `QuickSettingsFooter`, which is inited with no user session to test
// from the non-logged-in state to the logged-in state.
class QuickSettingsFooterTest : public NoSessionAshTestBase {
 public:
  QuickSettingsFooterTest() = default;
  QuickSettingsFooterTest(const QuickSettingsFooterTest&) = delete;
  QuickSettingsFooterTest& operator=(const QuickSettingsFooterTest&) = delete;
  ~QuickSettingsFooterTest() override = default;

  void SetUp() override {
    NoSessionAshTestBase::SetUp();
    widget_ = CreateFramelessTestWidget();
    widget_->SetFullscreen(true);
  }

  void TearDown() override {
    widget_.reset();
    NoSessionAshTestBase::TearDown();
  }

 protected:
  void SetUpView() {
    GetPrimaryUnifiedSystemTray()->ShowBubble();
    footer_ = widget_->SetContentsView(std::make_unique<QuickSettingsFooter>(
        GetPrimaryUnifiedSystemTray()
            ->bubble()
            ->unified_system_tray_controller()));
  }

  PillButton* GetSignOutButton() { return footer_->sign_out_button_; }

  views::Button* GetSettingsButton() { return footer_->settings_button_; }

  views::View* GetBatteryButton() {
    return footer_->GetViewByID(VIEW_ID_QS_BATTERY_BUTTON);
  }

  PowerButton* GetPowerButton() {
    return static_cast<PowerButton*>(
        footer_->GetViewByID(VIEW_ID_QS_POWER_BUTTON));
  }

  views::View* GetUserAvatar() {
    return footer_->GetViewByID(VIEW_ID_QS_USER_AVATAR_BUTTON);
  }

  void LayoutFooter() { views::test::RunScheduledLayout(footer_); }

 private:
  std::unique_ptr<views::Widget> widget_;

  // Owned by `widget_`.
  raw_ptr<QuickSettingsFooter, DanglingUntriaged> footer_;
};

// Tests that all buttons are with the correct view id, catalog name and UMA
// tracking.
TEST_F(QuickSettingsFooterTest, ButtonNamesAndUMA) {
  CreateUserSessions(2);
  SetUpView();

  // The number of view id should be the number of catalog name -1, since
  // `QsButtonCatalogName` has an extra `kUnknown` type.
  EXPECT_EQ(VIEW_ID_QS_MAX - VIEW_ID_QS_MIN,
            static_cast<int>(QsButtonCatalogName::kMaxValue) - 1);

  // No metrics logged before clicking on any buttons.
  auto histogram_tester = std::make_unique<base::HistogramTester>();
  histogram_tester->ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/0);

  // All buttons are visible and with the corresponding id.
  EXPECT_TRUE(GetSettingsButton()->GetVisible());
  EXPECT_EQ(VIEW_ID_QS_SETTINGS_BUTTON, GetSettingsButton()->GetID());

  EXPECT_TRUE(GetPowerButton()->GetVisible());
  EXPECT_EQ(VIEW_ID_QS_POWER_BUTTON, GetPowerButton()->GetID());

  ASSERT_TRUE(GetUserAvatar());
  EXPECT_TRUE(GetUserAvatar()->GetVisible());
  EXPECT_EQ(VIEW_ID_QS_USER_AVATAR_BUTTON, GetUserAvatar()->GetID());

  EXPECT_TRUE(GetBatteryButton()->GetVisible());
  EXPECT_EQ(VIEW_ID_QS_BATTERY_BUTTON, GetBatteryButton()->GetID());

  // Test the UMA tracking.
  LeftClickOn(GetPowerButton());

  histogram_tester->ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/1);
  histogram_tester->ExpectBucketCount("Ash.QuickSettings.Button.Activated",
                                      QsButtonCatalogName::kPowerButton,
                                      /*expected_count=*/1);

  // Close the power button menu.
  PressAndReleaseKey(ui::VKEY_ESCAPE);

  LeftClickOn(GetBatteryButton());
  histogram_tester->ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/2);
  histogram_tester->ExpectBucketCount("Ash.QuickSettings.Button.Activated",
                                      QsButtonCatalogName::kBatteryButton,
                                      /*expected_count=*/1);
}

// Settings button and avatar button are hidden before login.
TEST_F(QuickSettingsFooterTest, ButtonStatesNotLoggedIn) {
  SetUpView();

  EXPECT_FALSE(GetUserAvatar());
  EXPECT_FALSE(GetSettingsButton());
  EXPECT_TRUE(GetPowerButton()->GetVisible());
  EXPECT_TRUE(GetBatteryButton()->GetVisible());
  EXPECT_FALSE(GetSignOutButton());
}

// All buttons are shown after login.
TEST_F(QuickSettingsFooterTest, ButtonStatesLoggedIn) {
  CreateUserSessions(1);
  SetUpView();

  EXPECT_FALSE(GetSignOutButton());

  ASSERT_TRUE(GetSettingsButton());
  EXPECT_TRUE(GetSettingsButton()->GetVisible());

  ASSERT_TRUE(GetPowerButton());
  EXPECT_TRUE(GetPowerButton()->GetVisible());

  ASSERT_TRUE(GetBatteryButton());
  EXPECT_TRUE(GetBatteryButton()->GetVisible());

  // No sign-out button because there is only one account on the device.
  EXPECT_FALSE(GetSignOutButton());

  // No user avatar button because only one user is signed in.
  EXPECT_FALSE(GetUserAvatar());
}

// Settings button is hidden at the lock screen.
TEST_F(QuickSettingsFooterTest, ButtonStatesLockScreen) {
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  SetUpView();

  EXPECT_FALSE(GetSettingsButton());
  ASSERT_TRUE(GetPowerButton());
  EXPECT_TRUE(GetPowerButton()->GetVisible());
  ASSERT_TRUE(GetBatteryButton());
  EXPECT_TRUE(GetBatteryButton()->GetVisible());

  // No user avatar button because we are in the lock screen.
  EXPECT_FALSE(GetUserAvatar());
}

// Settings button and lock button are hidden when adding a second
// multiprofile user.
TEST_F(QuickSettingsFooterTest, ButtonStatesAddingUser) {
  CreateUserSessions(1);
  SetUserAddingScreenRunning(true);
  SetUpView();

  ASSERT_FALSE(GetUserAvatar());
  ASSERT_FALSE(GetSignOutButton());
  EXPECT_EQ(nullptr, GetSettingsButton());
  ASSERT_TRUE(GetPowerButton());
  EXPECT_TRUE(GetPowerButton()->GetVisible());
  ASSERT_TRUE(GetBatteryButton());
  EXPECT_TRUE(GetBatteryButton()->GetVisible());
}

TEST_F(QuickSettingsFooterTest, ButtonStatesGuestMode) {
  SimulateGuestLogin();
  SetUpView();

  ASSERT_TRUE(GetSettingsButton());
  EXPECT_TRUE(GetSettingsButton()->GetVisible());

  ASSERT_TRUE(GetPowerButton());
  EXPECT_TRUE(GetPowerButton()->GetVisible());

  ASSERT_TRUE(GetBatteryButton());
  EXPECT_TRUE(GetBatteryButton()->GetVisible());

  ASSERT_TRUE(GetSignOutButton());
  EXPECT_TRUE(GetSignOutButton()->GetVisible());
  EXPECT_EQ(u"Exit guest", GetSignOutButton()->GetText());
}

TEST_F(QuickSettingsFooterTest, ButtonStatesPublicAccount) {
  SimulateUserLogin("[email protected]", user_manager::UserType::kPublicAccount);
  SetUpView();

  ASSERT_TRUE(GetSettingsButton());
  EXPECT_TRUE(GetSettingsButton()->GetVisible());

  ASSERT_TRUE(GetPowerButton());
  EXPECT_TRUE(GetPowerButton()->GetVisible());

  ASSERT_TRUE(GetBatteryButton());
  EXPECT_TRUE(GetBatteryButton()->GetVisible());

  ASSERT_TRUE(GetSignOutButton());
  EXPECT_TRUE(GetSignOutButton()->GetVisible());
  EXPECT_EQ(u"Exit session", GetSignOutButton()->GetText());

  EXPECT_FALSE(GetUserAvatar());
}

TEST_F(QuickSettingsFooterTest, SignOutShowsWithMultipleAccounts) {
  GetSessionControllerClient()->set_existing_users_count(2);
  CreateUserSessions(1);
  SetUpView();

  ASSERT_TRUE(GetSignOutButton());
  EXPECT_TRUE(GetSignOutButton()->GetVisible());
  EXPECT_EQ(u"Sign out", GetSignOutButton()->GetText());

  // Although there are two accounts, only one is logged in so do not show the
  // user avatar.
  EXPECT_FALSE(GetUserAvatar());
}

TEST_F(QuickSettingsFooterTest, SignOutButtonRecordsUmaAndSignsOut) {
  // TODO(minch): Re-enable this test.
  if (features::IsForestFeatureEnabled()) {
    GTEST_SKIP() << "Skipping test body for forest feature.";
  }

  GetSessionControllerClient()->set_existing_users_count(2);
  CreateUserSessions(1);
  SetUpView();

  base::HistogramTester histogram_tester;
  LeftClickOn(GetSignOutButton());

  histogram_tester.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                    /*expected_count=*/1);
  histogram_tester.ExpectBucketCount("Ash.QuickSettings.Button.Activated",
                                     QsButtonCatalogName::kSignOutButton,
                                     /*expected_count=*/1);

  EXPECT_EQ(1, GetSessionControllerClient()->request_sign_out_count());
}

// Settings button is disabled when kSettingsIconDisabled is set.
TEST_F(QuickSettingsFooterTest, DisableSettingsIconPolicy) {
  GetSessionControllerClient()->AddUserSession(
      "[email protected]", user_manager::UserType::kRegular);
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::ACTIVE);
  SetUpView();
  EXPECT_EQ(views::Button::STATE_NORMAL, GetSettingsButton()->GetState());

  local_state()->SetBoolean(prefs::kOsSettingsEnabled, false);
  EXPECT_EQ(views::Button::STATE_DISABLED, GetSettingsButton()->GetState());

  local_state()->SetBoolean(prefs::kOsSettingsEnabled, true);
  EXPECT_EQ(views::Button::STATE_NORMAL, GetSettingsButton()->GetState());
}

// Tests different battery states.
TEST_F(QuickSettingsFooterTest, BatteryButtonState) {
  CreateUserSessions(1);
  SetUpView();

  const bool use_smart_charging_ui =
      ash::features::IsAdaptiveChargingEnabled() &&
      Shell::Get()
          ->adaptive_charging_controller()
          ->is_adaptive_delaying_charge();

  if (use_smart_charging_ui) {
    EXPECT_TRUE(views::IsViewClass<QsBatteryIconView>(GetBatteryButton()));
  } else {
    EXPECT_TRUE(views::IsViewClass<QsBatteryLabelView>(GetBatteryButton()));
  }
}

// The following tests will ensure that the entire Widget root view is properly
// laid out. The `LayoutFooter()` method will call
// `Widget::LayoutRootViewIfNecessary()`.
//
// Try to layout buttons before login.
TEST_F(QuickSettingsFooterTest, ButtonLayoutNotLoggedIn) {
  SetUpView();
  LayoutFooter();
}

// Try to layout buttons after login.
TEST_F(QuickSettingsFooterTest, ButtonLayoutLoggedIn) {
  CreateUserSessions(1);
  SetUpView();
  LayoutFooter();
}

// Try to layout buttons at the lock screen.
TEST_F(QuickSettingsFooterTest, ButtonLayoutLockScreen) {
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  SetUpView();
  LayoutFooter();
}

// Try to layout buttons when adding a second multiprofile user.
TEST_F(QuickSettingsFooterTest, ButtonLayoutAddingUser) {
  CreateUserSessions(1);
  SetUserAddingScreenRunning(true);
  SetUpView();
  LayoutFooter();
}

}  // namespace ash