chromium/ash/system/focus_mode/focus_mode_session_unittest.cc

// Copyright 2024 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_session.h"

#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

constexpr base::TimeDelta kEndDuration = base::Minutes(2);
constexpr base::TimeDelta kLongEndDuration =
    focus_mode_util::kMaximumDuration - base::Minutes(15);

}  // namespace

class FocusModeSessionTest : public testing::Test {
 public:
  FocusModeSessionTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  ~FocusModeSessionTest() override = default;

 protected:
  base::test::SingleThreadTaskEnvironment task_environment_;
};

// Tests the normal flow.
TEST_F(FocusModeSessionTest, NormalCase) {
  base::Time start_time = base::Time::Now();
  base::Time expected_end_time = kEndDuration + start_time;

  // Create a FocusModeSession and verify that it is in the on state with the
  // right end time.
  FocusModeSession session{kEndDuration, expected_end_time};
  EXPECT_EQ(FocusModeSession::State::kOn, session.GetState(start_time));
  EXPECT_EQ(expected_end_time, session.end_time());

  // Verify it is still running.
  task_environment_.AdvanceClock(base::Minutes(1));
  EXPECT_EQ(FocusModeSession::State::kOn, session.GetState(base::Time::Now()));
  EXPECT_EQ(expected_end_time, session.end_time());

  // Verify that we enter the ending moment when the session duration elapses.
  task_environment_.AdvanceClock(base::Minutes(1));
  EXPECT_EQ(FocusModeSession::State::kEnding,
            session.GetState(base::Time::Now()));
  EXPECT_EQ(expected_end_time, session.end_time());

  // Verify that the ending moment terminates correctly.
  task_environment_.AdvanceClock(base::Minutes(1));
  EXPECT_EQ(FocusModeSession::State::kOff, session.GetState(base::Time::Now()));
}

// Tests that `GetSnapshot` gives the correct results.
TEST_F(FocusModeSessionTest, GetSnapshot) {
  base::Time start_time = base::Time::Now();
  base::Time expected_end_time = kEndDuration + start_time;
  FocusModeSession session{kEndDuration, expected_end_time};

  auto validate_snapshot = [&](const std::string& trace_name,
                               const base::Time current_time,
                               const FocusModeSession::State expected_state,
                               const base::TimeDelta expected_session_duration,
                               const base::TimeDelta expected_time_elapsed,
                               const base::TimeDelta expected_remaining_time,
                               const double expected_progress) {
    SCOPED_TRACE(trace_name);
    FocusModeSession::Snapshot session_snapshot =
        session.GetSnapshot(current_time);
    EXPECT_EQ(expected_state, session_snapshot.state);
    EXPECT_EQ(expected_session_duration, session_snapshot.session_duration);
    EXPECT_EQ(expected_time_elapsed, session_snapshot.time_elapsed);
    EXPECT_EQ(expected_remaining_time, session_snapshot.remaining_time);
  };

  validate_snapshot("Initial kOn snapshot", /*current_time=*/base::Time::Now(),
                    /*expected_state=*/FocusModeSession::State::kOn,
                    /*expected_session_duration=*/kEndDuration,
                    /*expected_time_elapsed=*/base::TimeDelta(),
                    /*expected_remaining_time=*/kEndDuration,
                    /*expected_progress=*/0);

  task_environment_.AdvanceClock(base::Minutes(1));
  validate_snapshot("Partway through session snapshot",
                    /*current_time=*/base::Time::Now(),
                    /*expected_state=*/FocusModeSession::State::kOn,
                    /*expected_session_duration=*/kEndDuration,
                    /*expected_time_elapsed=*/base::Minutes(1),
                    /*expected_remaining_time=*/base::Minutes(1),
                    /*expected_progress=*/0.5);

  task_environment_.AdvanceClock(base::Minutes(1));
  validate_snapshot("kEnding snapshot", /*current_time=*/base::Time::Now(),
                    /*expected_state=*/FocusModeSession::State::kEnding,
                    /*expected_session_duration=*/kEndDuration,
                    /*expected_time_elapsed=*/base::Minutes(2),
                    /*expected_remaining_time=*/base::TimeDelta(),
                    /*expected_progress=*/1);
}

// Tests that adding time to the ongoing session works correctly, and does not
// go past the maximum.
TEST_F(FocusModeSessionTest, ExtendSession) {
  base::Time start_time = base::Time::Now();
  base::Time expected_long_end_time = kLongEndDuration + start_time;
  FocusModeSession session{kLongEndDuration, expected_long_end_time};
  EXPECT_EQ(FocusModeSession::State::kOn, session.GetState(start_time));
  EXPECT_EQ(expected_long_end_time, session.end_time());

  // Extend the session duration with no special cases.
  session.ExtendSession(base::Time::Now());
  EXPECT_EQ(kLongEndDuration + focus_mode_util::kExtendDuration,
            session.session_duration());
  EXPECT_EQ(expected_long_end_time + focus_mode_util::kExtendDuration,
            session.end_time());

  // Extends the session duraton, reaching the max duration.
  session.ExtendSession(base::Time::Now());
  EXPECT_EQ(focus_mode_util::kMaximumDuration, session.session_duration());
  EXPECT_EQ(start_time + focus_mode_util::kMaximumDuration, session.end_time());

  // Try to extend the time when already at the maximum duration.
  session.ExtendSession(base::Time::Now());
  EXPECT_EQ(focus_mode_util::kMaximumDuration, session.session_duration());
  EXPECT_EQ(start_time + focus_mode_util::kMaximumDuration, session.end_time());
}

// Tests that a session that is marked for having a persistent ending doesn't
// become `kOff` even when passing the previously expected ending time.
TEST_F(FocusModeSessionTest, PersistEnding) {
  base::Time start_time = base::Time::Now();
  base::Time expected_end_time = kEndDuration + start_time;
  FocusModeSession session{kEndDuration, expected_end_time};
  EXPECT_EQ(FocusModeSession::State::kOn, session.GetState(start_time));

  // Advance the clock to enter the ending moment.
  task_environment_.AdvanceClock(base::Minutes(2));
  EXPECT_EQ(FocusModeSession::State::kEnding,
            session.GetState(base::Time::Now()));

  // Mark the session to have a persistent ending.
  session.set_persistent_ending();
  EXPECT_TRUE(session.persistent_ending());

  // Verify that even past the normal ending moment time, we are still in the
  // `kEnding` state.
  task_environment_.AdvanceClock(base::Minutes(1));
  EXPECT_EQ(FocusModeSession::State::kEnding,
            session.GetState(base::Time::Now()));

  // Tests that extending the session will calculate the new increase from the
  // current time. This will also unlock `persistent_ending_` (set it to false).
  base::Time extend_timestamp = base::Time::Now();
  session.ExtendSession(extend_timestamp);
  FocusModeSession::Snapshot session_snapshot =
      session.GetSnapshot(extend_timestamp);
  EXPECT_EQ(FocusModeSession::State::kOn, session_snapshot.state);
  EXPECT_EQ(kEndDuration + focus_mode_util::kExtendDuration,
            session_snapshot.session_duration);
  EXPECT_EQ(focus_mode_util::kExtendDuration, session_snapshot.remaining_time);
  EXPECT_EQ(extend_timestamp + focus_mode_util::kExtendDuration,
            session.end_time());
  EXPECT_FALSE(session.persistent_ending());

  // Test that the session can advance into the kOff state, and doesn't get
  // locked to `kEnding`.
  task_environment_.AdvanceClock(base::Minutes(11));
  EXPECT_EQ(FocusModeSession::State::kOff, session.GetState(base::Time::Now()));
}

}  // namespace ash