chromium/ash/system/bluetooth/bluetooth_detailed_view_impl_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/bluetooth/bluetooth_detailed_view_impl.h"

#include <memory>

#include "ash/public/cpp/test/test_system_tray_client.h"
#include "ash/style/rounded_container.h"
#include "ash/style/switch.h"
#include "ash/system/bluetooth/bluetooth_device_list_item_view.h"
#include "ash/system/tray/fake_detailed_view_delegate.h"
#include "ash/system/tray/hover_highlight_view.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/constants/chromeos_features.h"
#include "mojo/public/cpp/bindings/clone_traits.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

using bluetooth_config::mojom::BluetoothDeviceProperties;
using bluetooth_config::mojom::BluetoothSystemState;
using bluetooth_config::mojom::PairedBluetoothDeviceProperties;
using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;

const char kDeviceId[] = "/device/id";

class FakeBluetoothDetailedViewDelegate
    : public BluetoothDetailedView::Delegate {
 public:
  FakeBluetoothDetailedViewDelegate() = default;
  ~FakeBluetoothDetailedViewDelegate() override = default;

  // BluetoothDetailedView::Delegate:
  void OnToggleClicked(bool new_state) override {
    last_toggle_state_ = new_state;
  }

  void OnPairNewDeviceRequested() override {
    ++pair_new_device_requested_count_;
  }

  void OnDeviceListItemSelected(
      const PairedBluetoothDevicePropertiesPtr& device) override {
    last_device_list_item_selected_ = mojo::Clone(device);
  }

  bool last_toggle_state_ = false;
  int pair_new_device_requested_count_ = 0;
  PairedBluetoothDevicePropertiesPtr last_device_list_item_selected_;
};

}  // namespace

class BluetoothDetailedViewImplTest : public AshTestBase,
                                      public testing::WithParamInterface<bool> {
 public:
  BluetoothDetailedViewImplTest() {
    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
    scoped_feature_list_->InitWithFeatureState(
        chromeos::features::kBluetoothWifiQSPodRefresh,
        IsBluetoothWifiQSPodRefreshEnabled());
  }

  void SetUp() override {
    AshTestBase::SetUp();

    auto bluetooth_detailed_view = std::make_unique<BluetoothDetailedViewImpl>(
        &detailed_view_delegate_, &bluetooth_detailed_view_delegate_);
    bluetooth_detailed_view_ = bluetooth_detailed_view.get();

    widget_ = CreateFramelessTestWidget();
    widget_->SetFullscreen(true);
    widget_->SetContentsView(bluetooth_detailed_view.release()->GetAsView());
  }

  void TearDown() override {
    widget_.reset();

    AshTestBase::TearDown();
  }

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

  bool IsBluetoothWifiQSPodRefreshEnabled() { return GetParam(); }

  HoverHighlightView* GetToggleRow() {
    return bluetooth_detailed_view_->toggle_row_;
  }

  Switch* GetToggleButton() { return bluetooth_detailed_view_->toggle_button_; }

  RoundedContainer* GetMainContainer() {
    return bluetooth_detailed_view_->main_container_;
  }

  views::Button* GetPairNewDeviceView() {
    return bluetooth_detailed_view_->pair_new_device_view_;
  }

  std::unique_ptr<views::Widget> widget_;
  FakeBluetoothDetailedViewDelegate bluetooth_detailed_view_delegate_;
  FakeDetailedViewDelegate detailed_view_delegate_;
  raw_ptr<BluetoothDetailedViewImpl, DanglingUntriaged>
      bluetooth_detailed_view_ = nullptr;
  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    BluetoothDetailedViewImplTest,
    /*IsBluetoothWifiQSPodRefreshEnabled()=*/testing::Bool());

TEST_P(BluetoothDetailedViewImplTest, PressingSettingsButtonOpensSettings) {
  views::Button* settings_button = GetSettingsButton();

  // Clicking the button at the lock screen does nothing.
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::LOCKED);
  LeftClickOn(settings_button);
  EXPECT_EQ(0, GetSystemTrayClient()->show_bluetooth_settings_count());
  EXPECT_EQ(0u, detailed_view_delegate_.close_bubble_call_count());

  // Clicking the button in an active user session opens OS settings.
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::ACTIVE);
  LeftClickOn(settings_button);
  EXPECT_EQ(1, GetSystemTrayClient()->show_bluetooth_settings_count());
  EXPECT_EQ(1u, detailed_view_delegate_.close_bubble_call_count());
}

TEST_P(BluetoothDetailedViewImplTest,
       UpdateBluetoothEnabledStateChangesUIState) {
  HoverHighlightView* toggle_row = GetToggleRow();
  Switch* toggle_button = GetToggleButton();
  RoundedContainer* main_container = GetMainContainer();
  views::Button* pair_new_device_view = GetPairNewDeviceView();

  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
      BluetoothSystemState::kEnabled);

  EXPECT_EQ(u"On", toggle_row->text_label()->GetText());
  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
            toggle_row->GetTooltipText());
  EXPECT_TRUE(toggle_button->GetIsOn());
  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
            toggle_button->GetTooltipText());
  EXPECT_TRUE(main_container->GetVisible());
  EXPECT_TRUE(pair_new_device_view->GetVisible());
  if (IsBluetoothWifiQSPodRefreshEnabled()) {
    EXPECT_FALSE(
        bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
  }
  EXPECT_TRUE(
      bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());

  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
      BluetoothSystemState::kDisabled);

  EXPECT_EQ(u"Off", toggle_row->text_label()->GetText());
  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is off.",
            toggle_row->GetTooltipText());
  EXPECT_FALSE(toggle_button->GetIsOn());
  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is off.",
            toggle_button->GetTooltipText());
  EXPECT_FALSE(main_container->GetVisible());
  if (IsBluetoothWifiQSPodRefreshEnabled()) {
    EXPECT_FALSE(
        bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
  }
  EXPECT_TRUE(
      bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());
  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
      BluetoothSystemState::kEnabling);
  EXPECT_EQ(u"On", toggle_row->text_label()->GetText());
  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
            toggle_row->GetTooltipText());
  EXPECT_TRUE(toggle_button->GetIsOn());
  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
            toggle_button->GetTooltipText());
  EXPECT_TRUE(main_container->GetVisible());
  EXPECT_FALSE(pair_new_device_view->GetVisible());
  if (IsBluetoothWifiQSPodRefreshEnabled()) {
    EXPECT_FALSE(
        bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
  }
  EXPECT_TRUE(
      bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());

  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
      BluetoothSystemState::kUnavailable);
  if (IsBluetoothWifiQSPodRefreshEnabled()) {
    EXPECT_TRUE(
        bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
    EXPECT_FALSE(
        bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());
  }
}

TEST_P(BluetoothDetailedViewImplTest, PressingToggleRowNotifiesDelegate) {
  HoverHighlightView* toggle_row = GetToggleRow();
  EXPECT_FALSE(bluetooth_detailed_view_delegate_.last_toggle_state_);

  LeftClickOn(toggle_row);

  EXPECT_TRUE(bluetooth_detailed_view_delegate_.last_toggle_state_);
}

TEST_P(BluetoothDetailedViewImplTest, PressingToggleButtonNotifiesDelegate) {
  Switch* toggle_button = GetToggleButton();
  views::Button* pair_new_device_view = GetPairNewDeviceView();

  EXPECT_FALSE(toggle_button->GetIsOn());
  EXPECT_FALSE(bluetooth_detailed_view_delegate_.last_toggle_state_);
  EXPECT_FALSE(pair_new_device_view->GetVisible());

  LeftClickOn(toggle_button);

  EXPECT_TRUE(toggle_button->GetIsOn());
  EXPECT_TRUE(bluetooth_detailed_view_delegate_.last_toggle_state_);
  EXPECT_FALSE(pair_new_device_view->GetVisible());
}

TEST_P(BluetoothDetailedViewImplTest, PressingPairNewDeviceNotifiesDelegate) {
  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
      BluetoothSystemState::kEnabled);
  views::test::RunScheduledLayout(bluetooth_detailed_view_);

  // Clicking the "pair new device" row notifies the delegate.
  views::Button* pair_new_device_view = GetPairNewDeviceView();
  LeftClickOn(pair_new_device_view);
  EXPECT_EQ(1,
            bluetooth_detailed_view_delegate_.pair_new_device_requested_count_);
}

TEST_P(BluetoothDetailedViewImplTest, SelectingDeviceListItemNotifiesDelegate) {
  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
      BluetoothSystemState::kEnabled);

  // Create a simulated device and add it to the list.
  PairedBluetoothDevicePropertiesPtr paired_properties =
      PairedBluetoothDeviceProperties::New();
  paired_properties->device_properties = BluetoothDeviceProperties::New();
  paired_properties->device_properties->id = kDeviceId;

  BluetoothDeviceListItemView* device_list_item =
      bluetooth_detailed_view_->AddDeviceListItem();
  device_list_item->UpdateDeviceProperties(
      /*device_index=*/0, /*total_device_count=*/0, paired_properties);

  bluetooth_detailed_view_->NotifyDeviceListChanged();

  // Clicking on the item notifies the delegate that the device was selected.
  LeftClickOn(device_list_item);
  EXPECT_TRUE(
      bluetooth_detailed_view_delegate_.last_device_list_item_selected_);
  EXPECT_EQ(kDeviceId,
            bluetooth_detailed_view_delegate_.last_device_list_item_selected_
                ->device_properties->id);
}

}  // namespace ash