chromium/ash/system/brightness/unified_brightness_view_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/brightness/unified_brightness_view.h"

#include "ash/strings/grit/ash_strings.h"
#include "ash/system/brightness/unified_brightness_slider_controller.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/memory/raw_ptr.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/slider.h"

namespace ash {

namespace {

constexpr float kMinBrightnessLevel = 0.05;

}  // namespace

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

  void SetUp() override {
    AshTestBase::SetUp();
    GetPrimaryUnifiedSystemTray()->ShowBubble();
    brightness_slider_controller_ =
        controller()->brightness_slider_controller_.get();
    unified_brightness_view_ = static_cast<UnifiedBrightnessView*>(
        controller()->unified_brightness_view_);
    brightness_slider_ =
        brightness_slider_controller_->CreateBrightnessSlider();
  }

  void TearDown() override {
    // Tests the environment tears down with the bubble closed.
    // In `UnifiedVolumeViewTest`, the environment is torn down with the bubble
    // open, so we can test both cases.
    brightness_slider_ = nullptr;
    GetPrimaryUnifiedSystemTray()->CloseBubble();
    AshTestBase::TearDown();
  }

  void WaitUntilUpdated() {
    task_environment()->FastForwardBy(base::Milliseconds(100));
    base::RunLoop().RunUntilIdle();
  }

  UnifiedBrightnessSliderController* brightness_slider_controller() {
    return brightness_slider_controller_;
  }

  UnifiedBrightnessView* unified_brightness_view() {
    return unified_brightness_view_;
  }

  UnifiedBrightnessView* brightness_slider() {
    return brightness_slider_.get();
  }

  views::Slider* slider() { return unified_brightness_view_->slider(); }

  views::Button* more_button() {
    return static_cast<UnifiedBrightnessView*>(
               controller()->unified_brightness_view_)
        ->more_button();
  }

  views::Button* night_light_button() {
    return static_cast<UnifiedBrightnessView*>(
               controller()->unified_brightness_view_)
        ->night_light_button();
  }

  const gfx::VectorIcon& GetIcon(float level) {
    return unified_brightness_view_->GetBrightnessIconForLevel(level);
  }

  UnifiedSystemTrayController* controller() {
    return GetPrimaryUnifiedSystemTray()
        ->bubble()
        ->unified_system_tray_controller();
  }

 private:
  // The `UnifiedBrightnessView` containing a `QuickSettingsSlider`, a
  // `NightLight` button, and a drill-in button.
  raw_ptr<UnifiedBrightnessView, DanglingUntriaged> unified_brightness_view_ =
      nullptr;

  // The `UnifiedBrightnessView` containing only a `QuickSettingsSlider`.
  std::unique_ptr<UnifiedBrightnessView> brightness_slider_ = nullptr;

  raw_ptr<UnifiedBrightnessSliderController, DanglingUntriaged>
      brightness_slider_controller_ = nullptr;
};

// Tests to ensure that the `slider_button` does not handle any events,
// letting them get through to the slider. Effectively the `slider_button` is
// part of the slider in the brightness view.
TEST_F(UnifiedBrightnessViewTest, SliderButtonClickThrough) {
  slider()->SetValue(1.0);
  EXPECT_FLOAT_EQ(unified_brightness_view()->slider()->GetValue(), 1.0);

  // A click on the `slider_button` for `unified_brightness_view()` should go
  // through to the slider and change the value to the minimum.
  LeftClickOn(unified_brightness_view()->slider_button());
  EXPECT_FLOAT_EQ(unified_brightness_view()->slider()->GetValue(), 0.0);
}

// Tests that `UnifiedBrightnessView` is made up of a `QuickSettingsSlider`, a
// `NightLight` button, and a drill-in button that leads to the display subpage.
TEST_F(UnifiedBrightnessViewTest, SliderButtonComponents) {
  EXPECT_EQ(unified_brightness_view()->children().size(), 3u);
  EXPECT_STREQ(unified_brightness_view()->children()[0]->GetClassName(),
               "QuickSettingsSlider");

  // TODO(b/257151067): Updates the a11y name id and tooltip text.
  auto* night_light_button =
      static_cast<IconButton*>(unified_brightness_view()->children()[1]);
  EXPECT_STREQ(night_light_button->GetClassName(), "IconButton");
  EXPECT_EQ(night_light_button->GetViewAccessibility().GetCachedName(),
            l10n_util::GetStringFUTF16(
                IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_TOGGLE_TOOLTIP,
                l10n_util::GetStringUTF16(
                    IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_DISABLED_STATE_TOOLTIP)));
  EXPECT_EQ(night_light_button->GetTooltipText(),
            u"Toggle Night Light. Night Light is off.");

  auto* display_subpage_drill_in_button =
      static_cast<IconButton*>(unified_brightness_view()->children()[2]);
  EXPECT_STREQ(display_subpage_drill_in_button->GetClassName(), "IconButton");
  EXPECT_EQ(
      display_subpage_drill_in_button->GetViewAccessibility().GetCachedName(),
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_SETTINGS_TOOLTIP));
  EXPECT_EQ(display_subpage_drill_in_button->GetTooltipText(),
            u"Show display settings");

  // Clicks on the drill-in button and checks `DisplayDetailedView` is shown.
  EXPECT_FALSE(controller()->IsDetailedViewShown());
  LeftClickOn(unified_brightness_view()->children()[2]);
  EXPECT_TRUE(controller()->showing_display_detailed_view());
}

// Tests that `UnifiedBrightnessView` in the display subpage is made up of a
// `QuickSettingsSlider`.
TEST_F(UnifiedBrightnessViewTest, SliderComponent) {
  EXPECT_EQ(brightness_slider()->children().size(), 1u);
  EXPECT_STREQ(brightness_slider()->children()[0]->GetClassName(),
               "QuickSettingsSlider");
}

// Tests the slider icon matches the slider level.
TEST_F(UnifiedBrightnessViewTest, SliderIcon) {
  const float levels[] = {0.0, 0.04, 0.2, 0.25, 0.49, 0.5, 0.7, 0.75, 0.9, 1};

  for (auto level : levels) {
    // Sets the slider value. `SetValue()` will pass in
    // `SliderChangeReason::kByApi` as the slider change reason and will not
    // trigger UI updates, so we should call `SliderValueChanged()` afterwards
    // to update the icon.
    slider()->SetValue(level);
    brightness_slider_controller()->SliderValueChanged(
        slider(), level, slider()->GetValue(),
        views::SliderChangeReason::kByUser);

    WaitUntilUpdated();

    // The minimum level for brightness is 0.05, since `SliderValueChanged()`
    // will adjust the brightness level and set the icon accordingly.
    const gfx::VectorIcon& icon = GetIcon(std::max(level, kMinBrightnessLevel));

    if (level <= 0.0) {
      EXPECT_STREQ(icon.name,
                   UnifiedBrightnessView::kBrightnessLevelIcons[1]->name);
    } else if (level <= 0.5) {
      EXPECT_STREQ(icon.name,
                   UnifiedBrightnessView::kBrightnessLevelIcons[1]->name);
    } else {
      EXPECT_STREQ(icon.name,
                   UnifiedBrightnessView::kBrightnessLevelIcons[2]->name);
    }
  }
}

// Tests that the `UnifiedBrightnessView` `more_button` is not enabled if and
// only if there is a trusted pinned window.
TEST_F(UnifiedBrightnessViewTest, MoreButton) {
  // At the start of the test, the system tray containing the brightness view is
  // already shown. Since there is no pinned window, the `more_button` should
  // not be disabled.
  EXPECT_TRUE(more_button()->GetEnabled());

  // Close the bubble so the brightness view can be recreated.
  GetPrimaryUnifiedSystemTray()->CloseBubble();

  // Create and trusted pin a window.
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  wm::ActivateWindow(window.get());
  window_util::PinWindow(window.get(), /*trusted=*/true);

  // Open the bubble and check that the new brightness view more button is in
  // the correct state.
  GetPrimaryUnifiedSystemTray()->ShowBubble();
  EXPECT_FALSE(more_button()->GetEnabled());

  // Close the bubble so the brightness view can be recreated.
  GetPrimaryUnifiedSystemTray()->CloseBubble();

  // Unpin the window
  WindowState::Get(window.get())->Restore();

  // Make sure the more button is not disabled.
  GetPrimaryUnifiedSystemTray()->ShowBubble();
  EXPECT_TRUE(more_button()->GetEnabled());
}

// Tests that the night light button is disabled in the sign-in screen, and is
// enabled in the locked screen.
TEST_F(UnifiedBrightnessViewTest, NightLightButtonState) {
  // Close the bubble so the brightness view can be recreated.
  GetPrimaryUnifiedSystemTray()->CloseBubble();

  // In the sign-in screen, the `night_light_button_` is disabled.
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::LOGIN_PRIMARY);
  GetPrimaryUnifiedSystemTray()->ShowBubble();
  EXPECT_FALSE(night_light_button()->GetEnabled());

  GetPrimaryUnifiedSystemTray()->CloseBubble();
  // In the locked screen, the `night_light_button_` is enabled.
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::LOCKED);
  GetPrimaryUnifiedSystemTray()->ShowBubble();
  EXPECT_TRUE(night_light_button()->GetEnabled());

  GetPrimaryUnifiedSystemTray()->CloseBubble();
  // In the active user session, the `night_light_button_` is enabled.
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::ACTIVE);
  GetPrimaryUnifiedSystemTray()->ShowBubble();
  EXPECT_TRUE(night_light_button()->GetEnabled());
}

}  // namespace ash