blob: 1931e4022cdc573b0e7a9c9b6a7191ee1b897c47 [file] [log] [blame]
// 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 "chromeos/ash/components/scalable_iph/scalable_iph.h"
#include <memory>
#include "base/feature_list.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ash/scalable_iph/mock_scalable_iph_delegate.h"
#include "chromeos/ash/components/scalable_iph/logger.h"
#include "chromeos/ash/components/scalable_iph/scalable_iph_constants.h"
#include "chromeos/ash/components/scalable_iph/scalable_iph_delegate.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/test/mock_tracker.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace scalable_iph {
namespace {
struct TransitionExpectation {
ScalableIphDelegate::SessionState from;
ScalableIphDelegate::SessionState to;
ScalableIph::TransitionSet transition;
};
constexpr char kTestTriggerEventParamName[] =
"IPH_ScalableIphUnlockedBasedOne_x_CustomConditionTriggerEvent";
const base::Feature& kTestFeature =
feature_engagement::kIPHScalableIphUnlockedBasedOneFeature;
} // namespace
// Test all 4*4 = 16 session state transitions.
TEST(ScalableIphTest, SessionStateTransitionTest) {
std::vector<TransitionExpectation> expectations;
// From `kUnknownInitialValue`:
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.to = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.transition = {}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.to = ScalableIphDelegate::SessionState::kActive,
.transition = {ScalableIph::SessionStateTransition::kAdvanceState,
ScalableIph::SessionStateTransition::kUnlock}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.to = ScalableIphDelegate::SessionState::kLocked,
.transition = {}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.to = ScalableIphDelegate::SessionState::kOther,
.transition = {}});
// From `kActive`:
// There should be no to=kUnknownInitialValue transition.
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kActive,
.to = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.transition = {}});
expectations.push_back({.from = ScalableIphDelegate::SessionState::kActive,
.to = ScalableIphDelegate::SessionState::kActive,
.transition = {}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kActive,
.to = ScalableIphDelegate::SessionState::kLocked,
.transition = {ScalableIph::SessionStateTransition::kAdvanceState}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kActive,
.to = ScalableIphDelegate::SessionState::kOther,
.transition = {ScalableIph::SessionStateTransition::kAdvanceState}});
// From `kLocked`:
// There should be no to=kUnknownInitialValue transition.
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kLocked,
.to = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.transition = {}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kLocked,
.to = ScalableIphDelegate::SessionState::kActive,
.transition = {ScalableIph::SessionStateTransition::kAdvanceState,
ScalableIph::SessionStateTransition::kUnlock}});
expectations.push_back({.from = ScalableIphDelegate::SessionState::kLocked,
.to = ScalableIphDelegate::SessionState::kLocked,
.transition = {}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kLocked,
.to = ScalableIphDelegate::SessionState::kOther,
.transition = {ScalableIph::SessionStateTransition::kAdvanceState}});
// From `kOther`:
// There should be no to=kUnknownInitialValue transition.
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kOther,
.to = ScalableIphDelegate::SessionState::kUnknownInitialValue,
.transition = {}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kOther,
.to = ScalableIphDelegate::SessionState::kActive,
.transition = {ScalableIph::SessionStateTransition::kAdvanceState}});
expectations.push_back(
{.from = ScalableIphDelegate::SessionState::kOther,
.to = ScalableIphDelegate::SessionState::kLocked,
.transition = {ScalableIph::SessionStateTransition::kAdvanceState}});
expectations.push_back({.from = ScalableIphDelegate::SessionState::kOther,
.to = ScalableIphDelegate::SessionState::kOther,
.transition = {}});
for (TransitionExpectation expectation : expectations) {
ScalableIph::TransitionSet expected = expectation.transition;
ScalableIph::TransitionSet actual =
ScalableIph::GetTransitionForTesting(expectation.from, expectation.to);
EXPECT_EQ(expected, actual)
<< "Expectation failed for transition from " << expectation.from
<< " to " << expectation.to << ". Expected transition is "
<< expected.ToString() << ". Actual transition is "
<< actual.ToString();
if (actual.Has(ScalableIph::SessionStateTransition::kUnlock)) {
EXPECT_TRUE(
actual.Has(ScalableIph::SessionStateTransition::kAdvanceState))
<< "kAdvanceState is a necessary condition of kUnlock";
EXPECT_EQ(expectation.to, ScalableIphDelegate::SessionState::kActive)
<< "kActive is a necessary condition of kUnlock";
}
}
}
TEST(ScalableIphTest, TriggerEventCondition) {
base::test::SingleThreadTaskEnvironment task_environment;
base::test::ScopedFeatureList scoped_feature_list;
base::FieldTrialParams params(
{{kTestTriggerEventParamName, kEventNameUnlocked}});
scoped_feature_list.InitAndEnableFeatureWithParameters(kTestFeature, params);
feature_engagement::test::MockTracker mock_tracker;
ScalableIph scalable_iph(
&mock_tracker, std::make_unique<ash::test::MockScalableIphDelegate>(),
std::make_unique<Logger>());
EXPECT_FALSE(scalable_iph.CheckTriggerEventForTesting(
kTestFeature, /*trigger_event=*/std::nullopt))
<< "If condition specified, the condition only satisfied if this check "
"is triggered by the specified event";
EXPECT_TRUE(scalable_iph.CheckTriggerEventForTesting(
kTestFeature, ScalableIph::Event::kUnlocked));
EXPECT_FALSE(scalable_iph.CheckTriggerEventForTesting(
kTestFeature, ScalableIph::Event::kFiveMinTick));
}
TEST(ScalableIphTest, TriggerEventConditionNotSpecified) {
base::test::SingleThreadTaskEnvironment task_environment;
base::test::ScopedFeatureList scoped_feature_list(kTestFeature);
feature_engagement::test::MockTracker mock_tracker;
ScalableIph scalable_iph(
&mock_tracker, std::make_unique<ash::test::MockScalableIphDelegate>(),
std::make_unique<Logger>());
EXPECT_TRUE(scalable_iph.CheckTriggerEventForTesting(
kTestFeature, /*trigger_event=*/std::nullopt))
<< "Condition always satisfied if this condition not specified";
EXPECT_TRUE(scalable_iph.CheckTriggerEventForTesting(
kTestFeature, ScalableIph::Event::kUnlocked))
<< "Condition always satisfied if this condition not specified";
}
} // namespace scalable_iph