chromium/ash/system/focus_mode/focus_mode_feature_pod_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/focus_mode/focus_mode_feature_pod_controller.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/glanceables/common/glanceables_util.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/focus_mode/focus_mode_controller.h"
#include "ash/system/focus_mode/focus_mode_detailed_view.h"
#include "ash/system/focus_mode/focus_mode_histogram_names.h"
#include "ash/system/focus_mode/focus_mode_task_test_utils.h"
#include "ash/system/unified/feature_tile.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 "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "components/user_manager/user_type.h"
#include "ui/views/view_utils.h"

namespace ash {

class FocusModeFeaturePodControllerTest : public AshTestBase {
 public:
  FocusModeFeaturePodControllerTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kFocusMode},
        /*disabled_features=*/{});
  }
  ~FocusModeFeaturePodControllerTest() override = default;

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

    // In order to create a detailed view, we need to check the network.
    // `g_network_handler` is null in tests, so we need to manually set the
    // network connected state.
    glanceables_util::SetIsNetworkConnectedForTest(true);

    // Focus Mode considers it to be a first time user flow if
    // `kFocusModeDoNotDisturb` has never been set by the user before. For
    // normal feature testing purposes, we will intentionally set it so that the
    // pref will not be marked as using the default value.
    prefs()->SetBoolean(prefs::kFocusModeDoNotDisturb, true);

    auto& tasks_client =
        CreateFakeTasksClient(AccountId::FromUserEmail("user0@tray"));
    AddFakeTaskList(tasks_client, "default");
    AddFakeTask(tasks_client, "default", "task1", "Task 1");

    CreateFakeFocusModeTile();
  }

  void TearDown() override {
    controller_.reset();
    tile_.reset();
    AshTestBase::TearDown();
  }

  void CreateFakeFocusModeTile() {
    // We need to show the bubble in order to get the
    // `unified_system_tray_controller`.
    GetPrimaryUnifiedSystemTray()->ShowBubble();
    controller_ = std::make_unique<FocusModeFeaturePodController>(
        GetPrimaryUnifiedSystemTray()
            ->bubble()
            ->unified_system_tray_controller());
    tile_ = controller_->CreateTile();
  }

  void ExpectFocusModeDetailedViewShown() {
    TrayDetailedView* detailed_view =
        GetPrimaryUnifiedSystemTray()
            ->bubble()
            ->quick_settings_view()
            ->GetDetailedViewForTest<TrayDetailedView>();
    ASSERT_TRUE(detailed_view);
    EXPECT_TRUE(views::IsViewClass<FocusModeDetailedView>(detailed_view));
  }

  PrefService* prefs() {
    return Shell::Get()->session_controller()->GetActivePrefService();
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<FocusModeFeaturePodController> controller_;
  std::unique_ptr<FeatureTile> tile_;
};

// Tests that the tile is normally visible, and is not visible when the screen
// is locked.
TEST_F(FocusModeFeaturePodControllerTest, TileVisibility) {
  // The tile is visible in an active session.
  EXPECT_TRUE(tile_->GetVisible());

  // Locking the screen closes the system tray bubble, so we need to create the
  // tile again.
  GetSessionControllerClient()->LockScreen();
  CreateFakeFocusModeTile();

  // Verify that the tile should not be visible at the lock screen.
  EXPECT_FALSE(tile_->GetVisible());
}

// Tests tile visibility for all different user types. This was added for
// b/344040908 to prevent crashes when ephemeral accounts try to use Focus Mode.
TEST_F(FocusModeFeaturePodControllerTest, TileVisibilityForUserTypes) {
  struct {
    std::string trace;
    user_manager::UserType user_type;
    bool is_tile_visible;
  } kUserTypeTestCases[] = {
      {"regular user", user_manager::UserType::kRegular, true},
      {"child", user_manager::UserType::kChild, true},
      {"guest", user_manager::UserType::kGuest, false},
      {"public account", user_manager::UserType::kPublicAccount, false},
      {"kiosk app", user_manager::UserType::kKioskApp, false},
      {"web kiosk app", user_manager::UserType::kWebKioskApp, false},
  };

  for (const auto& test_case : kUserTypeTestCases) {
    SCOPED_TRACE(test_case.trace);
    ClearLogin();
    SimulateUserLogin("[email protected]", test_case.user_type);

    CreateFakeFocusModeTile();
    EXPECT_EQ(test_case.is_tile_visible, tile_->GetVisible());
  }
}

// Tests that pressing the icon works and toggles a Focus Mode Session.
TEST_F(FocusModeFeaturePodControllerTest, PressIconTogglesFocusModeSession) {
  base::HistogramTester histogram_tester;
  auto* controller = FocusModeController::Get();
  EXPECT_FALSE(controller->in_focus_session());
  EXPECT_TRUE(tile_->GetVisible());
  EXPECT_TRUE(tile_->GetEnabled());

  // Verify that clicking the icon and starting the Focus Mode Session closes
  // the bubble.
  controller_->OnIconPressed();
  EXPECT_TRUE(controller->in_focus_session());
  EXPECT_FALSE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());

  // Recreate the tile since the bubble was closed.
  CreateFakeFocusModeTile();
  EXPECT_TRUE(tile_->IsToggled());

  // End the focus session. This should not close the bubble.
  controller_->OnIconPressed();
  EXPECT_FALSE(controller->in_focus_session());
  EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());
  EXPECT_FALSE(tile_->IsToggled());
  histogram_tester.ExpectBucketCount(
      /*name=*/focus_mode_histogram_names::
          kToggleEndButtonDuringSessionHistogramName,
      /*sample=*/focus_mode_histogram_names::ToggleSource::kFeaturePod,
      /*expected_count=*/1);
}

// Verify that when toggling a focus mode through the QS tile or the focus
// panel, the histogram will record it.
TEST_F(FocusModeFeaturePodControllerTest, CheckStartSessionSourceHistograms) {
  base::HistogramTester histogram_tester;

  auto* controller = FocusModeController::Get();
  EXPECT_FALSE(controller->in_focus_session());

  // 1. Start a focus session from the feature pod.
  controller->ToggleFocusMode(
      focus_mode_histogram_names::ToggleSource::kFeaturePod);
  EXPECT_TRUE(controller->in_focus_session());
  histogram_tester.ExpectBucketCount(
      /*name=*/focus_mode_histogram_names::kStartSessionSourceHistogramName,
      /*sample=*/focus_mode_histogram_names::StartSessionSource::kFeaturePod,
      /*expected_count=*/1);

  controller->ToggleFocusMode(
      focus_mode_histogram_names::ToggleSource::kFeaturePod);
  EXPECT_FALSE(controller->in_focus_session());

  // 2. Start a new focus session from the focus panel.
  controller->ToggleFocusMode();
  EXPECT_TRUE(controller->in_focus_session());
  histogram_tester.ExpectBucketCount(
      /*name=*/focus_mode_histogram_names::kStartSessionSourceHistogramName,
      /*sample=*/focus_mode_histogram_names::StartSessionSource::kFocusPanel,
      /*expected_count=*/1);

  histogram_tester.ExpectTotalCount(
      focus_mode_histogram_names::kStartSessionSourceHistogramName, 2);
}

// Tests that pressing the label works and shows the `FocusModeDetailedView`.
TEST_F(FocusModeFeaturePodControllerTest, PressLabelEntersFocusPanel) {
  controller_->OnLabelPressed();
  ExpectFocusModeDetailedViewShown();
}

// Verify that the tile operates correctly for the first time user flow. This
// includes:
// - The session duration is hidden.
// - Clicking the tile icon shows to the focus mode detailed view.
TEST_F(FocusModeFeaturePodControllerTest, FirstTimeUserFlow) {
  // Clear `kFocusModeDoNotDisturb` to trigger the first time user flow.
  prefs()->ClearPref(prefs::kFocusModeDoNotDisturb);

  // Recreate the tile so that the UI is updated after we set the user pref.
  CreateFakeFocusModeTile();

  // Verify that the tile sub label is hidden for the first time user flow.
  auto* focus_mode_controller = FocusModeController::Get();
  EXPECT_FALSE(focus_mode_controller->in_focus_session());
  EXPECT_FALSE(tile_->sub_label()->GetVisible());

  // Verify that clicking the icon does not start the focus session for the
  // first time user flow, but instead shows the focus mode detailed view.
  controller_->OnIconPressed();
  EXPECT_FALSE(focus_mode_controller->in_focus_session());
  ExpectFocusModeDetailedViewShown();

  // Start a session, and recreate the tile since the bubble was closed.
  focus_mode_controller->ToggleFocusMode();
  CreateFakeFocusModeTile();

  // Verify that the tile sub label should be visible.
  EXPECT_TRUE(focus_mode_controller->in_focus_session());
  EXPECT_TRUE(tile_->sub_label()->GetVisible());

  // End a session. Check that the tile session duration text is now visible,
  // and is not hidden since we are no longer in the first time user flow.
  focus_mode_controller->ToggleFocusMode();
  EXPECT_FALSE(focus_mode_controller->in_focus_session());
  EXPECT_TRUE(tile_->sub_label()->GetVisible());

  // Verify that clicking the icon goes back to the normal flow of and starting
  // a Focus Mode Session.
  controller_->OnIconPressed();
  EXPECT_TRUE(focus_mode_controller->in_focus_session());
}

}  // namespace ash