blob: a438f3e0abf142446514e49a8e0d901d5c6bb52b [file] [log] [blame]
// 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 "chrome/browser/user_education/user_education_configuration_provider.h"
#include <initializer_list>
#include <memory>
#include <vector>
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "components/feature_engagement/public/configuration.h"
#include "components/feature_engagement/public/feature_list.h"
#include "components/feature_engagement/public/group_list.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_education/common/feature_promo_handle.h"
#include "components/user_education/common/feature_promo_registry.h"
#include "components/user_education/common/feature_promo_specification.h"
#include "components/user_education/common/user_education_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/interaction/element_identifier.h"
namespace {
constexpr char kToastTrigger[] = "ToastIphFeature_trigger";
constexpr char kToastUsed[] = "ToastIphFeature_used";
constexpr char kSnoozeTrigger[] = "SnoozeIphFeature_trigger";
constexpr char kSnoozeUsed[] = "SnoozeIphFeature_used";
constexpr char kPerAppTrigger[] = "PerAppIphFeature_trigger";
constexpr char kPerAppUsed[] = "PerAppIphFeature_used";
constexpr char kLegalNoticeTrigger[] = "LegalNoticeIphFeature_trigger";
constexpr char kLegalNoticeUsed[] = "LegalNoticeIphFeature_used";
BASE_FEATURE(kToastIphFeature,
"IPH_ToastIphFeature",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kSnoozeIphFeature,
"IPH_SnoozeIphFeature",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kPerAppIphFeature,
"IPH_PerAppIphFeature",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kLegalNoticeIphFeature,
"IPH_LegalNoticeIphFeature",
base::FEATURE_ENABLED_BY_DEFAULT);
const std::initializer_list<const base::Feature*> kKnownFeatures{
&kToastIphFeature, &kSnoozeIphFeature, &kPerAppIphFeature,
&kLegalNoticeIphFeature};
const std::initializer_list<const base::Feature*> kKnownGroups{};
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestElementId);
// Provider must be created after possibly setting v2 feature flag.
//
// Call this only when the provider is actually needed for the test, and after
// SetEnableV2 (if necessary).
std::unique_ptr<UserEducationConfigurationProvider> CreateProvider() {
user_education::FeaturePromoRegistry registry;
registry.RegisterFeature(
user_education::FeaturePromoSpecification::CreateForToastPromo(
kToastIphFeature, kTestElementId, IDS_OK, IDS_CANCEL,
user_education::FeaturePromoSpecification::AcceleratorInfo(
IDC_HOME)));
registry.RegisterFeature(
user_education::FeaturePromoSpecification::CreateForSnoozePromo(
kSnoozeIphFeature, kTestElementId, IDS_CLOSE));
auto spec = user_education::FeaturePromoSpecification::CreateForCustomAction(
kPerAppIphFeature, kTestElementId, IDS_CANCEL, IDS_OK, base::DoNothing());
spec.set_promo_subtype_for_testing(
user_education::FeaturePromoSpecification::PromoSubtype::kPerApp);
registry.RegisterFeature(std::move(spec));
spec = user_education::FeaturePromoSpecification::CreateForCustomAction(
kLegalNoticeIphFeature, kTestElementId, IDS_CLEAR, IDS_CLOSE,
base::DoNothing());
spec.set_promo_subtype_for_testing(
user_education::FeaturePromoSpecification::PromoSubtype::kLegalNotice);
registry.RegisterFeature(std::move(spec));
return std::make_unique<UserEducationConfigurationProvider>(
std::move(registry));
}
} // namespace
class UserEducationConfigurationProviderTest : public testing::Test {
public:
UserEducationConfigurationProviderTest() {
kSessionRateImpactNone.type =
feature_engagement::SessionRateImpact::Type::NONE;
}
~UserEducationConfigurationProviderTest() override = default;
void SetEnableV2(bool enable_v2) {
if (enable_v2) {
feature_list_.InitAndEnableFeature(
user_education::features::kUserEducationExperienceVersion2);
} else {
feature_list_.InitAndDisableFeature(
user_education::features::kUserEducationExperienceVersion2);
}
}
auto GetDefaultTrigger(const char* name) {
return feature_engagement::EventConfig(
name, kLessThan3, feature_engagement::kMaxStoragePeriod,
feature_engagement::kMaxStoragePeriod);
}
auto GetAnyTrigger(const char* name) {
return feature_engagement::EventConfig(
name, kAny, feature_engagement::kMaxStoragePeriod,
feature_engagement::kMaxStoragePeriod);
}
auto GetDefaultUsed(const char* name) {
return feature_engagement::EventConfig(
name, kEqualsZero, feature_engagement::kMaxStoragePeriod,
feature_engagement::kMaxStoragePeriod);
}
protected:
const feature_engagement::Comparator kAny;
const feature_engagement::Blocking kBlockingAll;
const feature_engagement::BlockedBy kBlockedByAll;
const feature_engagement::Comparator kEqualsZero{feature_engagement::EQUAL,
0};
const feature_engagement::Comparator kLessThan3{feature_engagement::LESS_THAN,
3};
const feature_engagement::Comparator kAtLeast7{
feature_engagement::GREATER_THAN_OR_EQUAL, 7};
feature_engagement::SessionRateImpact kSessionRateImpactNone;
const feature_engagement::SessionRateImpact kSessionRateImpactAll;
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(UserEducationConfigurationProviderTest, ProvidesToastConfiguration) {
feature_engagement::FeatureConfig config;
EXPECT_TRUE(CreateProvider()->MaybeProvideFeatureConfiguration(
kToastIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_TRUE(config.valid);
EXPECT_EQ(GetDefaultUsed(kToastUsed), config.used);
EXPECT_EQ(GetDefaultTrigger(kToastTrigger), config.trigger);
EXPECT_TRUE(config.event_configs.empty());
EXPECT_EQ(kAny, config.session_rate);
EXPECT_EQ(kSessionRateImpactNone, config.session_rate_impact);
EXPECT_EQ(feature_engagement::BlockedBy(), config.blocked_by);
EXPECT_EQ(feature_engagement::Blocking(), config.blocking);
EXPECT_EQ(kAny, config.availability);
EXPECT_FALSE(config.tracking_only);
EXPECT_EQ(feature_engagement::SnoozeParams(), config.snooze_params);
EXPECT_TRUE(config.groups.empty());
}
TEST_F(UserEducationConfigurationProviderTest, ProvidesSnoozeConfiguration) {
feature_engagement::FeatureConfig config;
EXPECT_TRUE(CreateProvider()->MaybeProvideFeatureConfiguration(
kSnoozeIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_TRUE(config.valid);
EXPECT_EQ(GetDefaultUsed(kSnoozeUsed), config.used);
EXPECT_EQ(GetDefaultTrigger(kSnoozeTrigger), config.trigger);
EXPECT_TRUE(config.event_configs.empty());
EXPECT_EQ(kEqualsZero, config.session_rate);
EXPECT_EQ(kSessionRateImpactAll, config.session_rate_impact);
EXPECT_EQ(feature_engagement::BlockedBy(), config.blocked_by);
EXPECT_EQ(feature_engagement::Blocking(), config.blocking);
EXPECT_EQ(kAny, config.availability);
EXPECT_FALSE(config.tracking_only);
EXPECT_EQ(feature_engagement::SnoozeParams(), config.snooze_params);
EXPECT_TRUE(config.groups.empty());
}
TEST_F(UserEducationConfigurationProviderTest, ProvidesPerAppConfiguration) {
feature_engagement::FeatureConfig config;
EXPECT_TRUE(CreateProvider()->MaybeProvideFeatureConfiguration(
kPerAppIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_TRUE(config.valid);
EXPECT_EQ(GetDefaultUsed(kPerAppUsed), config.used);
EXPECT_EQ(GetAnyTrigger(kPerAppTrigger), config.trigger);
EXPECT_TRUE(config.event_configs.empty());
EXPECT_EQ(kAny, config.session_rate);
EXPECT_EQ(kSessionRateImpactAll, config.session_rate_impact);
EXPECT_EQ(feature_engagement::BlockedBy(), config.blocked_by);
EXPECT_EQ(feature_engagement::Blocking(), config.blocking);
EXPECT_EQ(kAny, config.availability);
EXPECT_FALSE(config.tracking_only);
EXPECT_EQ(feature_engagement::SnoozeParams(), config.snooze_params);
EXPECT_TRUE(config.groups.empty());
}
TEST_F(UserEducationConfigurationProviderTest,
ProvidesLegalNoticeConfiguration) {
feature_engagement::FeatureConfig config;
EXPECT_TRUE(CreateProvider()->MaybeProvideFeatureConfiguration(
kLegalNoticeIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_TRUE(config.valid);
EXPECT_EQ(GetDefaultUsed(kLegalNoticeUsed), config.used);
EXPECT_EQ(GetAnyTrigger(kLegalNoticeTrigger), config.trigger);
EXPECT_TRUE(config.event_configs.empty());
EXPECT_EQ(kAny, config.session_rate);
EXPECT_EQ(kSessionRateImpactAll, config.session_rate_impact);
EXPECT_EQ(feature_engagement::BlockedBy(), config.blocked_by);
EXPECT_EQ(feature_engagement::Blocking(), config.blocking);
EXPECT_EQ(kAny, config.availability);
EXPECT_FALSE(config.tracking_only);
EXPECT_EQ(feature_engagement::SnoozeParams(), config.snooze_params);
EXPECT_TRUE(config.groups.empty());
}
TEST_F(UserEducationConfigurationProviderTest, HandlesEventConfigs) {
feature_engagement::EventConfig event("other_event", kEqualsZero, 100, 100);
feature_engagement::FeatureConfig config;
config.event_configs.insert(event);
EXPECT_TRUE(CreateProvider()->MaybeProvideFeatureConfiguration(
kSnoozeIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_EQ(kAtLeast7, config.availability);
EXPECT_THAT(config.event_configs, testing::ElementsAre(event));
}
TEST_F(UserEducationConfigurationProviderTest, DoesntOverwriteNames) {
feature_engagement::FeatureConfig config;
config.trigger.name = "foo";
config.used.name = "bar";
EXPECT_TRUE(CreateProvider()->MaybeProvideFeatureConfiguration(
kSnoozeIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_EQ(GetDefaultTrigger("foo"), config.trigger);
EXPECT_EQ(GetDefaultUsed("bar"), config.used);
}
TEST_F(UserEducationConfigurationProviderTest, v1_DoesntOverwriteValid) {
SetEnableV2(false);
feature_engagement::FeatureConfig config;
const feature_engagement::EventConfig trigger(
"foo", feature_engagement::Comparator(feature_engagement::LESS_THAN, 10),
100, 99);
const feature_engagement::EventConfig used(
"bar", feature_engagement::Comparator(feature_engagement::LESS_THAN, 8),
98, 97);
config.trigger = trigger;
config.used = used;
config.valid = true;
EXPECT_FALSE(CreateProvider()->MaybeProvideFeatureConfiguration(
kSnoozeIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_EQ(trigger, config.trigger);
EXPECT_EQ(used, config.used);
}
TEST_F(UserEducationConfigurationProviderTest, v2_DoesOverwriteValid) {
SetEnableV2(true);
feature_engagement::FeatureConfig config;
const feature_engagement::EventConfig trigger(
"foo", feature_engagement::Comparator(feature_engagement::LESS_THAN, 10),
100, 99);
const feature_engagement::EventConfig used(
"bar", feature_engagement::Comparator(feature_engagement::LESS_THAN, 8),
98, 97);
config.trigger = trigger;
config.used = used;
config.valid = true;
EXPECT_TRUE(CreateProvider()->MaybeProvideFeatureConfiguration(
kSnoozeIphFeature, config, kKnownFeatures, kKnownGroups));
EXPECT_EQ(GetDefaultTrigger("foo"), config.trigger);
EXPECT_EQ(GetDefaultUsed("bar"), config.used);
}