chromium/ash/system/video_conference/bubble/vc_tile_ui_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/video_conference/bubble/vc_tile_ui_controller.h"

#include <memory>
#include <optional>
#include <string_view>

#include "ash/constants/ash_features.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/video_conference/effects/fake_video_conference_tray_effects_manager.h"
#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
#include "ash/system/video_conference/video_conference_utils.h"
#include "ash/test/ash_test_base.h"
#include "ash/utility/haptics_tracking_test_input_controller.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/ash/components/dbus/dlcservice/fake_dlcservice_client.h"
#include "third_party/cros_system_api/dbus/dlcservice/dbus-constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/devices/haptic_touchpad_effects.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/widget/widget.h"

namespace ash::video_conference {

// A class that waits for a given `FeatureTile`'s download state to change.
class FeatureTileDownloadStateWaiter : public FeatureTile::Observer {
 public:
  FeatureTileDownloadStateWaiter() = default;
  FeatureTileDownloadStateWaiter(const FeatureTileDownloadStateWaiter&) =
      delete;
  FeatureTileDownloadStateWaiter& operator=(
      const FeatureTileDownloadStateWaiter&) = delete;
  ~FeatureTileDownloadStateWaiter() override = default;

  // Waits for `tile`'s download state to change before proceeding. Note: this
  // method should only be called when a change to `tile`'s download state is
  // actually expected to happen. If no change happens, then this waits forever
  // (until the test times out).
  void Wait(FeatureTile* tile, FeatureTile::DownloadState expected_state) {
    base::ScopedObservation<FeatureTile, FeatureTile::Observer>
        scoped_observation{this};
    scoped_observation.Observe(tile);
    future_.Clear();
    EXPECT_EQ(expected_state, future_.Take());
  }

 private:
  // Featuretile::Observer:
  void OnDownloadStateChanged(FeatureTile::DownloadState download_state,
                              int progress) override {
    future_.SetValue(download_state);
  }

  base::test::TestFuture<FeatureTile::DownloadState> future_;
};

class VcTileUiControllerTest : public AshTestBase {
 public:
  VcTileUiControllerTest() = default;
  VcTileUiControllerTest(const VcTileUiControllerTest&) = delete;
  VcTileUiControllerTest& operator=(const VcTileUiControllerTest&) = delete;
  ~VcTileUiControllerTest() override = default;

  // AshTestBase:
  void SetUp() override {
    scoped_feature_list_
        .InitWithFeatures(/*enabled_features=*/
                          {features::kFeatureManagementVideoConference,
                           features::kVcDlcUi},
                          /*disabled_features=*/{});
    DlcserviceClient::InitializeFake();

    // Tests need to use a fake controller for the video conference tray, which
    // is present when `features::IsVideoConferenceEnabled()` is true. A
    // `FakeVideoConferenceTrayEffectsManager` is also used so that the set of
    // DLCs associated with a VC effect can be configured in the tests.
    fake_vc_effect_manager_ =
        std::make_unique<FakeVideoConferenceTrayEffectsManager>();
    fake_vc_tray_controller_ =
        std::make_unique<FakeVideoConferenceTrayController>();
    fake_vc_tray_controller_->SetEffectsManager(fake_vc_effect_manager_.get());

    AshTestBase::SetUp();

    // Create a test VC toggle effect.
    toggle_effect_ = std::make_unique<VcHostedEffect>(
        /*type=*/VcEffectType::kToggle,
        /*get_state_callback=*/
        base::BindRepeating(&VcTileUiControllerTest::GetEffectState,
                            base::Unretained(this)),
        /*effect_id=*/VcEffectId::kTestEffect);
    toggle_effect_->AddState(std::make_unique<VcEffectState>(
        /*icon=*/&kVideoConferenceNoiseCancellationOnIcon,
        /*label_text=*/u"Dummy label",
        /*accessible_name_id=*/
        IDS_ASH_STATUS_TRAY_AUDIO_INPUT_NOISE_CANCELLATION,
        /*button_callback=*/
        base::BindRepeating(&VcTileUiControllerTest::ButtonCallback,
                            base::Unretained(this))));
    test_controller_ =
        std::make_unique<VcTileUiController>(toggle_effect_.get());
    haptics_tracker_ = std::make_unique<HapticsTrackingTestInputController>();

    // Create a test `views::Widget` and place the test tile in it.
    test_widget_ = CreateFramelessTestWidget();
    test_widget_->SetBounds(gfx::Rect(0, 0, 100, 100));
    auto test_tile = test_controller()->CreateTile();
    test_tile_ = test_tile->GetWeakPtr();
    test_widget_->SetContentsView(std::move(test_tile));
  }
  void TearDown() override {
    test_widget_.reset();
    haptics_tracker_.reset();
    test_controller_.reset();
    AshTestBase::TearDown();
    fake_vc_tray_controller_.reset();
    fake_vc_effect_manager_.reset();
    DlcserviceClient::Shutdown();
  }

  VcEffectId GetTestEffectId() { return test_controller()->effect_id_; }

  // Returns the count of haptics effects since the test started. If `toggle_on`
  // is true then the returned count corresponds to the "toggle on" haptic, and
  // if it is false then the returned count corresponds to the "toggle off"
  // haptic.
  int GetHapticsToggleCount(bool toggle_on) {
    return haptics_tracker_->GetSentHapticCount(
        toggle_on ? ui::HapticTouchpadEffect::kToggleOn
                  : ui::HapticTouchpadEffect::kToggleOff,
        ui::HapticTouchpadEffectStrength::kMedium);
  }

  // Returns the expected tooltip text given the toggle state.
  std::u16string GetToggleButtonExpectedTooltipText(bool toggle_on) {
    return l10n_util::GetStringFUTF16(
        VIDEO_CONFERENCE_TOGGLE_BUTTON_TOOLTIP,
        l10n_util::GetStringUTF16(
            IDS_ASH_STATUS_TRAY_AUDIO_INPUT_NOISE_CANCELLATION),
        l10n_util::GetStringUTF16(
            toggle_on ? VIDEO_CONFERENCE_TOGGLE_BUTTON_STATE_ON
                      : VIDEO_CONFERENCE_TOGGLE_BUTTON_STATE_OFF));
  }

  void TrackToggleUMA(bool target_toggle_state) {
    test_controller()->TrackToggleUMA(target_toggle_state);
  }

  void PlayToggleHaptic(bool target_toggle_state) {
    test_controller()->PlayToggleHaptic(target_toggle_state);
  }

  void SetInitialToggleState(bool target_toggle_state) {
    initial_toggle_state_ = target_toggle_state;
  }

  void ButtonCallback() { ++button_callback_invocation_count_; }

  size_t GetButtonCallbackInvocationCount() {
    return button_callback_invocation_count_;
  }

  void SetDlcError(std::string_view id, std::string_view error) {
    static_cast<FakeDlcserviceClient*>(DlcserviceClient::Get())
        ->set_get_dlc_state_error(id, error);
  }

  void SetDlcState(std::string_view id,
                   dlcservice::DlcState_State state,
                   double progress,
                   bool notify = false) {
    FakeDlcserviceClient* fake_dlcservice_client =
        static_cast<FakeDlcserviceClient*>(DlcserviceClient::Get());
    dlcservice::DlcState dlc_state;
    dlc_state.set_id(std::string(id));
    dlc_state.set_state(state);
    dlc_state.set_progress(progress);
    fake_dlcservice_client->set_dlc_state(id, dlc_state);
    if (notify) {
      fake_dlcservice_client->NotifyObserversForTest(dlc_state);
    }
  }

  FeatureTile* test_tile() { return test_tile_.get(); }
  VcTileUiController* test_controller() { return test_controller_.get(); }
  FakeVideoConferenceTrayEffectsManager* effects_manager() {
    return fake_vc_effect_manager_.get();
  }

 private:
  std::optional<int> GetEffectState() { return initial_toggle_state_; }

  base::test::ScopedFeatureList scoped_feature_list_;

  std::unique_ptr<FakeVideoConferenceTrayEffectsManager>
      fake_vc_effect_manager_;
  std::unique_ptr<FakeVideoConferenceTrayController> fake_vc_tray_controller_;
  std::unique_ptr<VcHostedEffect> toggle_effect_;
  std::unique_ptr<views::Widget> test_widget_;
  std::unique_ptr<VcTileUiController> test_controller_;
  std::unique_ptr<HapticsTrackingTestInputController> haptics_tracker_;
  base::WeakPtr<FeatureTile> test_tile_;
  bool initial_toggle_state_ = false;
  size_t button_callback_invocation_count_ = 0;
};

// Tests that the tile's initial toggle state matches the state of the
// corresponding VC effect.
TEST_F(VcTileUiControllerTest, InitialToggleState) {
  // Explicitly set the initial effect state to be off.
  SetInitialToggleState(false);

  // Verify that a tile initialized with this state is toggled-off.
  EXPECT_FALSE(test_controller()->CreateTile()->IsToggled());

  // Explicitly set the initial effect state to be on.
  SetInitialToggleState(true);

  // Verify that a tile initialized with this state is toggled-on.
  EXPECT_TRUE(test_controller()->CreateTile()->IsToggled());
}

// Tests that the tile's initial tooltip matches the state of the corresponding
// VC effect.
TEST_F(VcTileUiControllerTest, InitialTooltip) {
  // Explicitly set the initial effect state to be off.
  SetInitialToggleState(false);

  // Verify that a tile initialized with this state is using a "toggled-off"
  // tooltip.
  EXPECT_EQ(GetToggleButtonExpectedTooltipText(/*toggle_on=*/false),
            test_controller()->CreateTile()->GetTooltipText());

  // Explicitly set the initial effect state to be on.
  SetInitialToggleState(true);

  // Verify that a tile initialized with this state is using a "toggled-on"
  // tooltip.
  EXPECT_EQ(GetToggleButtonExpectedTooltipText(/*toggle_on=*/true),
            test_controller()->CreateTile()->GetTooltipText());
}

// Tests that toggling the tile invokes the effect state's button callback.
TEST_F(VcTileUiControllerTest, EffectStateCallbackInvokedWhenToggled) {
  // Verify that the callback has not been called yet.
  ASSERT_EQ(0u, GetButtonCallbackInvocationCount());

  // Toggle the tile.
  LeftClickOn(test_tile());

  // Verify that the callback has been invoked once.
  ASSERT_EQ(1u, GetButtonCallbackInvocationCount());

  // Toggle the tile again.
  LeftClickOn(test_tile());

  // Verify that the callback has been invoked twice.
  ASSERT_EQ(2u, GetButtonCallbackInvocationCount());
}

// Tests that pressing the tile causes it to change its toggle state.
TEST_F(VcTileUiControllerTest, TogglesWhenPressed) {
  // Verify that the test tile is not toggled.
  ASSERT_FALSE(test_tile()->IsToggled());

  // Press the test tile.
  LeftClickOn(test_tile());

  // Verify that the test tile is now toggled.
  EXPECT_TRUE(test_tile()->IsToggled());

  // Press the test tile again.
  LeftClickOn(test_tile());

  // Verify that the test tile is now not toggled.
  EXPECT_FALSE(test_tile()->IsToggled());
}

// Tests that the tooltip is updated when the tile is toggled.
TEST_F(VcTileUiControllerTest, UpdatesTooltipWhenToggled) {
  // Toggle the test tile on.
  LeftClickOn(test_tile());
  ASSERT_TRUE(test_tile()->IsToggled());

  // Verify that the "toggled-on" tooltip is being used.
  EXPECT_EQ(GetToggleButtonExpectedTooltipText(/*toggle_on=*/true),
            test_tile()->GetTooltipText());

  // Toggle the test tile off.
  LeftClickOn(test_tile());

  // Verify that the "toggled-off" tooltip is now being used.
  EXPECT_EQ(GetToggleButtonExpectedTooltipText(/*toggle_on=*/false),
            test_tile()->GetTooltipText());
}

// Tests the download state of a tile whose effect is not associated with any
// DLC.
TEST_F(VcTileUiControllerTest, NoDownloadStateWhenNoDlc) {
  // Remove any potential associations with DLC from the tile's effect.
  effects_manager()->SetDlcIdsForEffectId(VcEffectId::kTestEffect, {});

  // Verify that a tile created for the effect has a download state of
  // `FeatureTile::DownloadState::kNone`.
  EXPECT_EQ(FeatureTile::DownloadState::kNone,
            test_controller()->CreateTile()->download_state_for_testing());
}

// Tests the initial download states of a tile whose effect is associated with a
// single DLC.
TEST_F(VcTileUiControllerTest, InitialDownloadStatesForSingleDlc) {
  // Associate the tile's effect with a single DLC.
  const std::string dlc_id = "test-dlc";
  effects_manager()->SetDlcIdsForEffectId(VcEffectId::kTestEffect, {dlc_id});

  // Case: DLC has an error.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kError` state.
  SetDlcError(dlc_id, dlcservice::kErrorNeedReboot);
  std::unique_ptr<FeatureTile> tile = test_controller()->CreateTile();

  FeatureTileDownloadStateWaiter tile_waiter;
  tile_waiter.Wait(tile.get(),
                   /*expected_state=*/FeatureTile::DownloadState::kError);
  EXPECT_EQ(FeatureTile::DownloadState::kError,
            tile->download_state_for_testing());

  // Reset the DLC error for the following test cases.
  SetDlcError(dlc_id, dlcservice::kErrorNone);

  // Case: DLC is downloading.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kDownloading`
  // state.
  SetDlcState(/*id=*/dlc_id,
              /*state=*/::dlcservice::DlcState_State::DlcState_State_INSTALLING,
              /*progress=*/0.42);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  EXPECT_EQ(42, tile->download_progress_for_testing());

  // Case: DLC successfully finished downloading.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kDownloaded`
  // state.
  SetDlcState(/*id=*/"test-dlc",
              /*state=*/::dlcservice::DlcState_State::DlcState_State_INSTALLED,
              /*progress=*/1);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloaded);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloaded,
            tile->download_state_for_testing());
}

// Tests the initial download states of a tile whose effect is associated with
// multiple DLCs.
TEST_F(VcTileUiControllerTest, InitialDownloadStatesForMultipleDlcs) {
  // Associate the tile's effect with multiple DLCs.
  const std::string dlc_id_1 = "test-dlc-1";
  const std::string dlc_id_2 = "test-dlc-2";
  effects_manager()->SetDlcIdsForEffectId(VcEffectId::kTestEffect,
                                          {dlc_id_1, dlc_id_2});

  // Case: One of the DLCs has an error.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kError` state.
  SetDlcError(dlc_id_1, dlcservice::kErrorNeedReboot);
  std::unique_ptr<FeatureTile> tile = test_controller()->CreateTile();
  FeatureTileDownloadStateWaiter tile_waiter;
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kError);
  EXPECT_EQ(FeatureTile::DownloadState::kError,
            tile->download_state_for_testing());

  // Case: Some DLCs have an error, some are fully installed.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kError` state.
  SetDlcState(dlc_id_2, dlcservice::DlcState_State::DlcState_State_INSTALLED,
              /*progress=*/1);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kError);
  EXPECT_EQ(FeatureTile::DownloadState::kError,
            tile->download_state_for_testing());

  // Case: All of the DLCs have errors.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kError` state.
  SetDlcError(dlc_id_2, dlcservice::kErrorNeedReboot);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kError);
  EXPECT_EQ(FeatureTile::DownloadState::kError,
            tile->download_state_for_testing());

  // Reset all DLC errors for the following test cases.
  SetDlcError(dlc_id_1, dlcservice::kErrorNone);
  SetDlcError(dlc_id_2, dlcservice::kErrorNone);

  // Case: All DLCs installed.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kDownloaded`
  // state.
  SetDlcState(dlc_id_1, dlcservice::DlcState_State::DlcState_State_INSTALLED,
              /*progress=*/1);
  SetDlcState(dlc_id_2, dlcservice::DlcState_State::DlcState_State_INSTALLED,
              /*progress=*/1);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloaded);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloaded,
            tile->download_state_for_testing());

  // Case: Some DLCs installed, some still in progress.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kDownloading`
  // state.
  SetDlcState(dlc_id_1, dlcservice::DlcState_State::DlcState_State_INSTALLED,
              /*progress=*/1);
  SetDlcState(dlc_id_2, dlcservice::DlcState_State::DlcState_State_INSTALLING,
              /*progress=*/0.5);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  EXPECT_EQ(75, tile->download_progress_for_testing());

  // Case: All DLCs still in progress.
  // Expectation: Tile starts in `FeatureTile::DownloadState::kDownloading`
  // state.
  SetDlcState(dlc_id_1, dlcservice::DlcState_State::DlcState_State_INSTALLING,
              /*progress=*/0.25);
  SetDlcState(dlc_id_2, dlcservice::DlcState_State::DlcState_State_INSTALLING,
              /*progress=*/0.75);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  EXPECT_EQ(50, tile->download_progress_for_testing());
}

// Tests that a tile's download state changes to reflect DLC updates, when the
// tile's effect is associated with a single DLC.
TEST_F(VcTileUiControllerTest, ChangingDownloadStateForSingleDlc) {
  // Associate the tile's effect with a single DLC.
  const std::string dlc_id = "test-dlc";
  effects_manager()->SetDlcIdsForEffectId(VcEffectId::kTestEffect, {dlc_id});

  // Case: DLC is installing, then completes successfully.
  // Expectation: Tile changes to `FeatureTile::DownloadState::kDownloaded`
  // state.
  SetDlcState(dlc_id, dlcservice::DlcState_State::DlcState_State_INSTALLING,
              /*progress=*/0.42);
  std::unique_ptr<FeatureTile> tile = test_controller()->CreateTile();
  FeatureTileDownloadStateWaiter tile_waiter;
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  ASSERT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  SetDlcState(dlc_id, dlcservice::DlcState_State::DlcState_State_INSTALLED,
              /*progress=*/1, /*notify=*/true);
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloaded);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloaded,
            tile->download_state_for_testing());

  // Case: DLC is installing, then has an error.
  // Expectation: Tile changes to `FeatureTile::DownloadState::kError` state.
  SetDlcState(dlc_id, dlcservice::DlcState_State::DlcState_State_INSTALLING,
              /*progress=*/0.42);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  ASSERT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  SetDlcError(dlc_id, dlcservice::kErrorNeedReboot);
  SetDlcState(dlc_id, dlcservice::DlcState_State::DlcState_State_NOT_INSTALLED,
              /*progress=*/0, /*notify=*/true);
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kError);
  EXPECT_EQ(FeatureTile::DownloadState::kError,
            tile->download_state_for_testing());
}

// Tests that a tile's download state changes to reflect DLC updates, when the
// tile's effect is associated with multiple DLCs.
TEST_F(VcTileUiControllerTest, ChangingDownloadStateForMultipleDlcs) {
  // Associate the tile's effect with multiple DLCs.
  const std::string dlc_id_1 = "test-dlc-1";
  const std::string dlc_id_2 = "test-dlc-2";
  effects_manager()->SetDlcIdsForEffectId(VcEffectId::kTestEffect,
                                          {dlc_id_1, dlc_id_2});

  // Case: All DLCs are installing, then just one completes successfully (others
  // are still installing).
  // Expectation: Tile remains in `FeatureTile::DownloadState::kDownloading`
  // state.
  SetDlcState(dlc_id_1, dlcservice::DlcState_State_INSTALLING,
              /*progress=*/0.42);
  SetDlcState(dlc_id_2, dlcservice::DlcState_State_INSTALLING,
              /*progress=*/0.58);
  std::unique_ptr<FeatureTile> tile = test_controller()->CreateTile();
  FeatureTileDownloadStateWaiter tile_waiter;
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  ASSERT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  SetDlcState(dlc_id_1, dlcservice::DlcState_State_INSTALLED, /*progress=*/1,
              /*notify=*/true);
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());

  // Case: Some DLCs are installed and others are still installing, then the
  // still-installing DLCs complete successfully.
  // Expectation: Tile changes to `FeatureTile::DownloadState::kDownloaded`
  // state.
  SetDlcState(dlc_id_1, dlcservice::DlcState_State_INSTALLING,
              /*progress=*/0.42);
  SetDlcState(dlc_id_2, dlcservice::DlcState_State_INSTALLED,
              /*progress=*/1);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  ASSERT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  SetDlcState(dlc_id_1, dlcservice::DlcState_State_INSTALLED, /*progress=*/1,
              /*notify=*/true);
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloaded);
  EXPECT_EQ(FeatureTile::DownloadState::kDownloaded,
            tile->download_state_for_testing());

  // Case: Some DLCs are installed and others are still installing, then the
  // still-installing DLCs have an error.
  // Expectation: Tile changes to `FeatureTile::DownloadState::kError` state.
  SetDlcState(dlc_id_1, dlcservice::DlcState_State_INSTALLING,
              /*progress=*/0.42);
  SetDlcState(dlc_id_2, dlcservice::DlcState_State_INSTALLED,
              /*progress=*/1);
  tile = test_controller()->CreateTile();
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kDownloading);
  ASSERT_EQ(FeatureTile::DownloadState::kDownloading,
            tile->download_state_for_testing());
  SetDlcError(dlc_id_1, dlcservice::kErrorNeedReboot);
  SetDlcState(dlc_id_1,
              dlcservice::DlcState_State::DlcState_State_NOT_INSTALLED,
              /*progress=*/0, /*notify=*/true);
  tile_waiter.Wait(tile.get(), FeatureTile::DownloadState::kError);
  EXPECT_EQ(FeatureTile::DownloadState::kError,
            tile->download_state_for_testing());
}

// Tests that `VcTileUiController` records toggle metrics when it is instructed
// to.
TEST_F(VcTileUiControllerTest, RecordsHistogramForToggle) {
  // "Toggle-off" test case.
  {
    base::HistogramTester histogram_tester;

    // Instruct the tile controller to track a "toggle-off".
    TrackToggleUMA(/*target_toggle_state=*/false);

    // Verify that the "toggle-off" was recorded.
    histogram_tester.ExpectUniqueSample(
        video_conference_utils::GetEffectHistogramNameForClick(
            GetTestEffectId()),
        /*sample=*/0, /*expected_bucket_count=*/1);
  }

  // "Toggle-on" test case.
  {
    base::HistogramTester histogram_tester;

    // Instruct the tile controller to track a "toggle-on".
    TrackToggleUMA(/*target_toggle_state=*/true);

    // Verify that the "toggle-on" was recorded.
    histogram_tester.ExpectUniqueSample(
        video_conference_utils::GetEffectHistogramNameForClick(
            GetTestEffectId()),
        /*sample=*/1, /*expected_bucket_count=*/1);
  }
}

// Tests that `VcTileUiController` plays haptic toggle effects when it is
// instructed to.
TEST_F(VcTileUiControllerTest, PlaysHapticsForToggle) {
  // Assert there have been no haptics so far.
  ASSERT_EQ(0, GetHapticsToggleCount(/*toggle_on=*/false));
  ASSERT_EQ(0, GetHapticsToggleCount(/*toggle_on=*/true));

  // Instruct the tile controller to play a "toggle-off" haptic.
  PlayToggleHaptic(/*target_toggle_state=*/false);

  // Verify that the "toggle-off" haptic was played.
  EXPECT_EQ(1, GetHapticsToggleCount(/*toggle_on=*/false));
  EXPECT_EQ(0, GetHapticsToggleCount(/*toggle_on=*/true));

  // Instruct the tile controller to play a "toggle-on" haptic.
  PlayToggleHaptic(/*target_toggle_state=*/true);

  // Verify that the "toggle-on" haptic was played.
  EXPECT_EQ(1, GetHapticsToggleCount(/*toggle_on=*/false));
  EXPECT_EQ(1, GetHapticsToggleCount(/*toggle_on=*/true));
}

}  // namespace ash::video_conference