#include "ash/system/audio/output_audio_sliders_view.h"
#include <memory>
#include <vector>
#include "ash/system/tray/hover_highlight_view.h"
#include "ash/test/ash_test_base.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "media/base/media_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
constexpr uint64_t kInternalSpeakerId = 10001;
constexpr uint64_t kHeadphoneId = 10002;
struct AudioNodeInfo {
constexpr AudioNodeInfo(const uint64_t id,
const char* const device_name,
const char* const type,
const char* const name)
: id(id), device_name(device_name), type(type), name(name) {}
const uint64_t id;
const char* const device_name;
const char* const type;
const char* const name;
};
constexpr AudioNodeInfo kInternalSpeaker(kInternalSpeakerId,
"Fake Speaker",
"INTERNAL_SPEAKER",
"Speaker");
constexpr AudioNodeInfo kHeadphone(kHeadphoneId,
"Fake Headphone",
"HEADPHONE",
"Headphone");
AudioNode GenerateAudioNode(const AudioNodeInfo& node_info) {
return AudioNode(false, node_info.id,
false,
node_info.id,
0, node_info.device_name,
node_info.type, node_info.name,
false, 0,
2,
0,
25);
}
AudioNodeList GenerateAudioNodeList(
const std::vector<AudioNodeInfo>& node_infos) {
AudioNodeList node_list(node_infos.size());
std::ranges::transform(node_infos, node_list.begin(),
[](const AudioNodeInfo& node_info) {
return GenerateAudioNode(node_info);
});
return node_list;
}
}
class OutputAudioSlidersViewTest : public AshTestBase {
public:
MOCK_METHOD(void, OnDeviceListUpdated, (const bool has_devices), ());
void SetUp() override {
feature_list_.InitAndEnableFeature(media::kBackgroundListening);
AshTestBase::SetUp();
widget_ = CreateFramelessTestWidget();
widget_->SetFullscreen(true);
}
void TearDown() override {
view_ = nullptr;
widget_.reset();
AshTestBase::TearDown();
}
void CreateView() {
view_ = widget_->SetContentsView(std::make_unique<OutputAudioSlidersView>(
base::BindRepeating(&OutputAudioSlidersViewTest::OnDeviceListUpdated,
base::Unretained(this))));
}
std::vector<raw_ptr<views::View, VectorExperimental>>
GetContainerChildViews() {
return view_->GetSliderContainerForTesting()->children();
}
HoverHighlightView* FindView(uint64_t device_id) {
auto device_map = view_->GetMapForTesting();
auto it = std::ranges::find(
device_map, device_id, [](const AudioDeviceViewMap::value_type& value) {
return value.second.id;
});
return it == device_map.end()
? nullptr
: views::AsViewClass<HoverHighlightView>(it->first);
}
void LayoutEntriesIfNecessary() {
view_->GetWidget()->LayoutRootViewIfNecessary();
}
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<views::Widget> widget_;
raw_ptr<OutputAudioSlidersView> view_ = nullptr;
};
TEST_F(OutputAudioSlidersViewTest, RendersSliderCorrectly) {
CreateView();
FakeCrasAudioClient::Get()->SetAudioNodesAndNotifyObserversForTesting(
GenerateAudioNodeList({kInternalSpeaker, kHeadphone}));
CrasAudioHandler::Get()->SwitchToDevice(
AudioDevice(GenerateAudioNode(kInternalSpeaker)),
true,
DeviceActivateType::kActivateByUser);
EXPECT_EQ(kInternalSpeakerId,
CrasAudioHandler::Get()->GetPrimaryActiveOutputNode());
HoverHighlightView* internal_speaker_slider = FindView(kInternalSpeakerId);
ASSERT_TRUE(internal_speaker_slider);
EXPECT_TRUE(internal_speaker_slider->GetVisible());
EXPECT_EQ(internal_speaker_slider->text_label()->GetText(),
u"Speaker (internal)");
HoverHighlightView* headphone_slider = FindView(kHeadphoneId);
ASSERT_TRUE(headphone_slider);
EXPECT_TRUE(headphone_slider->GetVisible());
EXPECT_EQ(headphone_slider->text_label()->GetText(), u"Headphones");
CrasAudioHandler::Get()->SwitchToDevice(
AudioDevice(GenerateAudioNode(kHeadphone)),
true,
DeviceActivateType::kActivateByUser);
EXPECT_EQ(kHeadphoneId,
CrasAudioHandler::Get()->GetPrimaryActiveOutputNode());
internal_speaker_slider = FindView(kInternalSpeakerId);
ASSERT_TRUE(internal_speaker_slider);
EXPECT_TRUE(internal_speaker_slider->GetVisible());
EXPECT_EQ(u"Speaker (internal)",
internal_speaker_slider->text_label()->GetText());
headphone_slider = FindView(kHeadphoneId);
ASSERT_TRUE(headphone_slider);
EXPECT_TRUE(headphone_slider->GetVisible());
EXPECT_EQ(u"Headphones", headphone_slider->text_label()->GetText());
}
TEST_F(OutputAudioSlidersViewTest, UpdateDevices) {
EXPECT_CALL(*this, OnDeviceListUpdated);
CreateView();
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceListUpdated).Times(2);
FakeCrasAudioClient::Get()->SetAudioNodesAndNotifyObserversForTesting(
GenerateAudioNodeList({kInternalSpeaker, kHeadphone}));
LayoutEntriesIfNecessary();
EXPECT_EQ(GetContainerChildViews().size(), 2u);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceListUpdated).Times(2);
FakeCrasAudioClient::Get()->SetAudioNodesAndNotifyObserversForTesting(
GenerateAudioNodeList({kInternalSpeaker}));
LayoutEntriesIfNecessary();
EXPECT_EQ(GetContainerChildViews().size(), 1u);
testing::Mock::VerifyAndClearExpectations(this);
}
}