| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/feature_engagement/internal/tracker_impl.h" |
| |
| #include <map> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/metrics/user_action_tester.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "components/feature_engagement/internal/availability_model_impl.h" |
| #include "components/feature_engagement/internal/display_lock_controller.h" |
| #include "components/feature_engagement/internal/editable_configuration.h" |
| #include "components/feature_engagement/internal/event_model_impl.h" |
| #include "components/feature_engagement/internal/feature_config_condition_validator.h" |
| #include "components/feature_engagement/internal/in_memory_event_store.h" |
| #include "components/feature_engagement/internal/multiple_event_model_provider.h" |
| #include "components/feature_engagement/internal/never_availability_model.h" |
| #include "components/feature_engagement/internal/never_event_storage_validator.h" |
| #include "components/feature_engagement/internal/once_condition_validator.h" |
| #include "components/feature_engagement/internal/single_event_model_provider.h" |
| #include "components/feature_engagement/internal/stats.h" |
| #include "components/feature_engagement/internal/test/test_time_provider.h" |
| #include "components/feature_engagement/internal/time_provider.h" |
| #include "components/feature_engagement/public/configuration.h" |
| #include "components/feature_engagement/public/feature_constants.h" |
| #include "components/feature_engagement/public/feature_list.h" |
| #include "components/feature_engagement/public/session_controller.h" |
| #include "components/feature_engagement/test/scoped_iph_feature_list.h" |
| #include "components/feature_engagement/test/test_tracker.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace feature_engagement { |
| |
| namespace { |
| BASE_FEATURE(kTrackerTestFeatureFoo, |
| "test_foo", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| BASE_FEATURE(kTrackerTestFeatureBar, |
| "test_bar", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| BASE_FEATURE(kTrackerTestFeatureBaz, |
| "test_baz", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| BASE_FEATURE(kTrackerTestFeatureQux, |
| "test_qux", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| BASE_FEATURE(kTrackerTestFeatureEvent, |
| "test_event", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| BASE_FEATURE(kTrackerTestFeatureSnooze, |
| "test_snooze", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| BASE_FEATURE(kTrackerTestFeatureDeviceStorage, |
| "test_device_storage", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| BASE_FEATURE(kTrackerTestGroupOne, |
| "test_group_one", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| void RegisterFeatureConfig(EditableConfiguration* configuration, |
| const base::Feature& feature, |
| bool valid, |
| bool tracking_only, |
| bool snooze_params, |
| const char* additional_event_name = nullptr, |
| StorageType storage_type = StorageType::PROFILE) { |
| FeatureConfig config; |
| config.valid = valid; |
| config.used.name = feature.name + std::string("_used"); |
| config.trigger.name = feature.name + std::string("_trigger"); |
| config.trigger.storage = 1u; |
| config.groups = {"test_group_one"}; |
| config.tracking_only = tracking_only; |
| if (snooze_params) { |
| config.snooze_params.snooze_interval = 7u; |
| config.snooze_params.max_limit = 3u; |
| } |
| if (additional_event_name) { |
| EventConfig event_config; |
| event_config.name = additional_event_name; |
| event_config.comparator.type = GREATER_THAN_OR_EQUAL; |
| event_config.comparator.value = 2U; |
| event_config.window = 7U; |
| event_config.storage = 7U; |
| config.event_configs.emplace(std::move(event_config)); |
| } |
| config.storage_type = storage_type; |
| configuration->SetConfiguration(&feature, config); |
| } |
| |
| void RegisterGroupConfig(EditableConfiguration* configuration, |
| const base::Feature& group, |
| bool valid) { |
| GroupConfig config; |
| config.valid = valid; |
| config.trigger.name = group.name + std::string("_trigger"); |
| config.trigger.storage = 1u; |
| configuration->SetConfiguration(&group, config); |
| } |
| |
| // An OnInitializedCallback that stores whether it has been invoked and what |
| // the result was. |
| class StoringInitializedCallback { |
| public: |
| StoringInitializedCallback() : invoked_(false), success_(false) {} |
| |
| StoringInitializedCallback(const StoringInitializedCallback&) = delete; |
| StoringInitializedCallback& operator=(const StoringInitializedCallback&) = |
| delete; |
| |
| void OnInitialized(bool success) { |
| DCHECK(!invoked_); |
| invoked_ = true; |
| success_ = success; |
| } |
| |
| bool invoked() { return invoked_; } |
| |
| bool success() { return success_; } |
| |
| private: |
| bool invoked_; |
| bool success_; |
| }; |
| |
| // An InMemoryEventStore that is able to fake successful and unsuccessful |
| // loading of state. |
| class TestTrackerInMemoryEventStore : public InMemoryEventStore { |
| public: |
| explicit TestTrackerInMemoryEventStore(bool load_should_succeed) |
| : load_should_succeed_(load_should_succeed) {} |
| |
| TestTrackerInMemoryEventStore(const TestTrackerInMemoryEventStore&) = delete; |
| TestTrackerInMemoryEventStore& operator=( |
| const TestTrackerInMemoryEventStore&) = delete; |
| |
| void Load(OnLoadedCallback callback) override { |
| HandleLoadResult(std::move(callback), load_should_succeed_); |
| } |
| |
| void WriteEvent(const Event& event) override { |
| events_[event.name()] = event; |
| } |
| |
| Event GetEvent(const std::string& event_name) { return events_[event_name]; } |
| |
| private: |
| // Denotes whether the call to Load(...) should succeed or not. This impacts |
| // both the ready-state and the result for the OnLoadedCallback. |
| bool load_should_succeed_; |
| |
| std::map<std::string, Event> events_; |
| }; |
| |
| class StoreEverythingEventStorageValidator : public EventStorageValidator { |
| public: |
| StoreEverythingEventStorageValidator() = default; |
| |
| StoreEverythingEventStorageValidator( |
| const StoreEverythingEventStorageValidator&) = delete; |
| StoreEverythingEventStorageValidator& operator=( |
| const StoreEverythingEventStorageValidator&) = delete; |
| |
| ~StoreEverythingEventStorageValidator() override = default; |
| |
| bool ShouldStore(const std::string& event_name) const override { |
| return true; |
| } |
| |
| bool ShouldKeep(const std::string& event_name, |
| uint32_t event_day, |
| uint32_t current_day) const override { |
| return true; |
| } |
| }; |
| |
| class TestTrackerAvailabilityModel : public AvailabilityModel { |
| public: |
| TestTrackerAvailabilityModel() : ready_(true) {} |
| |
| TestTrackerAvailabilityModel(const TestTrackerAvailabilityModel&) = delete; |
| TestTrackerAvailabilityModel& operator=(const TestTrackerAvailabilityModel&) = |
| delete; |
| |
| ~TestTrackerAvailabilityModel() override = default; |
| |
| void Initialize(AvailabilityModel::OnInitializedCallback callback, |
| uint32_t current_day) override { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), ready_)); |
| } |
| |
| bool IsReady() const override { return ready_; } |
| |
| void SetIsReady(bool ready) { ready_ = ready; } |
| |
| std::optional<uint32_t> GetAvailability( |
| const base::Feature& feature) const override { |
| return std::nullopt; |
| } |
| |
| private: |
| bool ready_; |
| }; |
| |
| class TestTrackerDisplayLockController : public DisplayLockController { |
| public: |
| TestTrackerDisplayLockController() = default; |
| |
| TestTrackerDisplayLockController(const TestTrackerDisplayLockController&) = |
| delete; |
| TestTrackerDisplayLockController& operator=( |
| const TestTrackerDisplayLockController&) = delete; |
| |
| ~TestTrackerDisplayLockController() override = default; |
| |
| std::unique_ptr<DisplayLockHandle> AcquireDisplayLock() override { |
| return std::move(next_display_lock_handle_); |
| } |
| |
| bool IsDisplayLocked() const override { return false; } |
| |
| void SetNextDisplayLockHandle( |
| std::unique_ptr<DisplayLockHandle> display_lock_handle) { |
| next_display_lock_handle_ = std::move(display_lock_handle); |
| } |
| |
| private: |
| // The next DisplayLockHandle to return. |
| std::unique_ptr<DisplayLockHandle> next_display_lock_handle_; |
| }; |
| |
| class TestTrackerEventExporter : public TrackerEventExporter { |
| public: |
| TestTrackerEventExporter() = default; |
| |
| ~TestTrackerEventExporter() override = default; |
| |
| void ExportEvents(ExportEventsCallback callback) override { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), events_to_export_)); |
| } |
| |
| void SetEventsToExport(std::vector<EventData> events) { |
| events_to_export_ = events; |
| } |
| |
| base::WeakPtr<TestTrackerEventExporter> AsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| // The events to export |
| std::vector<EventData> events_to_export_; |
| |
| base::WeakPtrFactory<TestTrackerEventExporter> weak_ptr_factory_{this}; |
| }; |
| |
| class TestSessionController : public SessionController { |
| public: |
| TestSessionController() : should_reset_for_next_call_(false) {} |
| ~TestSessionController() override = default; |
| |
| bool ShouldResetSession() override { return should_reset_for_next_call_; } |
| |
| void SetShouldResetForNextCall(bool should_reset_for_next_call) { |
| should_reset_for_next_call_ = should_reset_for_next_call; |
| } |
| |
| private: |
| bool should_reset_for_next_call_; |
| }; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| class TestConfigurationProvider : public ConfigurationProvider { |
| public: |
| TestConfigurationProvider() = default; |
| ~TestConfigurationProvider() override = default; |
| |
| // ConfigurationProvider: |
| bool MaybeProvideFeatureConfiguration( |
| const base::Feature& feature, |
| feature_engagement::FeatureConfig& config, |
| const feature_engagement::FeatureVector& known_features, |
| const feature_engagement::GroupVector& known_groups) const override { |
| config = config_; |
| return true; |
| } |
| |
| const char* GetConfigurationSourceDescription() const override { |
| return "Test Configuration Provider"; |
| } |
| |
| std::set<std::string> MaybeProvideAllowedEventPrefixes( |
| const base::Feature& feature) const override { |
| return {}; |
| } |
| |
| void SetConfig(const FeatureConfig& config) { config_ = config; } |
| |
| private: |
| FeatureConfig config_; |
| }; |
| #endif |
| |
| class TrackerImplTest : public ::testing::Test { |
| public: |
| TrackerImplTest() = default; |
| |
| TrackerImplTest(const TrackerImplTest&) = delete; |
| TrackerImplTest& operator=(const TrackerImplTest&) = delete; |
| |
| void SetUp() override { |
| std::unique_ptr<EditableConfiguration> configuration = |
| std::make_unique<EditableConfiguration>(); |
| configuration_ = configuration.get(); |
| |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureFoo, |
| true /* is_valid */, false /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureBar, |
| true /* is_valid */, false /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureBaz, |
| true /* is_valid */, true /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureQux, |
| false /* is_valid */, false /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureEvent, |
| /*valid=*/true, /*tracking_only=*/false, |
| /*snooze_params=*/false, "test_event_event"); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureSnooze, |
| true /* is_valid */, false /* tracking_only */, |
| true /* snooze_params */); |
| RegisterGroupConfig(configuration.get(), kTrackerTestGroupOne, |
| true /* is_valid */); |
| |
| std::unique_ptr<TestTrackerInMemoryEventStore> event_store = |
| CreateEventStore(); |
| event_store_ = event_store.get(); |
| |
| auto event_model = std::make_unique<EventModelImpl>( |
| std::move(event_store), |
| std::make_unique<StoreEverythingEventStorageValidator>()); |
| |
| auto event_model_provider = |
| std::make_unique<SingleEventModelProvider>(std::move(event_model)); |
| |
| auto availability_model = std::make_unique<TestTrackerAvailabilityModel>(); |
| availability_model_ = availability_model.get(); |
| availability_model_->SetIsReady(ShouldAvailabilityStoreBeReady()); |
| |
| auto display_lock_controller = |
| std::make_unique<TestTrackerDisplayLockController>(); |
| display_lock_controller_ = display_lock_controller.get(); |
| |
| auto condition_validator = CreateConditionValidator(); |
| condition_validator_ = condition_validator.get(); |
| |
| auto time_provider = std::make_unique<TestTimeProvider>(); |
| time_provider_ = time_provider.get(); |
| time_provider->SetCurrentDay(1u); |
| |
| auto event_exporter = std::make_unique<TestTrackerEventExporter>(); |
| event_exporter_ = event_exporter.get(); |
| |
| auto session_controller = std::make_unique<TestSessionController>(); |
| session_controller_ = session_controller.get(); |
| |
| tracker_ = std::make_unique<TrackerImpl>( |
| std::move(event_model_provider), std::move(availability_model), |
| std::move(configuration), std::move(display_lock_controller), |
| std::move(condition_validator), std::move(time_provider), |
| std::move(event_exporter), std::move(session_controller), |
| nullptr /* event_storage_migration */, nullptr /* pref_service */); |
| } |
| |
| void VerifyEventTrigger(std::string event_name, uint32_t count) { |
| Event trigger_event = event_store_->GetEvent(event_name); |
| if (count == 0) { |
| EXPECT_EQ(0, trigger_event.events_size()); |
| return; |
| } |
| |
| EXPECT_EQ(1, trigger_event.events_size()); |
| EXPECT_EQ(1u, trigger_event.events(0).day()); |
| EXPECT_EQ(count, trigger_event.events(0).count()); |
| } |
| |
| void VerifyEventTriggerEvents(const base::Feature& feature, uint32_t count) { |
| VerifyEventTrigger(configuration_->GetFeatureConfig(feature).trigger.name, |
| count); |
| } |
| |
| void VerifyGroupEventTriggerEvents(const base::Feature& group, |
| uint32_t count) { |
| VerifyEventTrigger(configuration_->GetGroupConfig(group).trigger.name, |
| count); |
| } |
| |
| void VerifyHistogramsForFeature(const std::string& histogram_name, |
| bool check, |
| int expected_success_count, |
| int expected_failure_count, |
| int expected_success_tracking_only_count) { |
| if (!check) |
| return; |
| |
| histogram_tester_.ExpectBucketCount( |
| histogram_name, static_cast<int>(stats::TriggerHelpUIResult::SUCCESS), |
| expected_success_count); |
| histogram_tester_.ExpectBucketCount( |
| histogram_name, static_cast<int>(stats::TriggerHelpUIResult::FAILURE), |
| expected_failure_count); |
| histogram_tester_.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(stats::TriggerHelpUIResult::SUCCESS_TRACKING_ONLY), |
| expected_success_tracking_only_count); |
| } |
| |
| // Histogram values are checked only if their respective |check_...| is true, |
| // since inspecting a bucket count for a histogram that has not been recorded |
| // yet leads to an error. |
| void VerifyHistograms(bool check_foo, |
| int expected_foo_success_count, |
| int expected_foo_failure_count, |
| int expected_foo_success_tracking_only_count, |
| bool check_bar, |
| int expected_bar_success_count, |
| int expected_bar_failure_count, |
| int expected_bar_success_tracking_only_count, |
| bool check_baz, |
| int expected_baz_success_count, |
| int expected_baz_failure_count, |
| int expected_baz_success_tracking_only_count, |
| bool check_qux, |
| int expected_qux_success_count, |
| int expected_qux_failure_count, |
| int expected_qux_success_tracking_only_count) { |
| VerifyHistogramsForFeature("InProductHelp.ShouldTriggerHelpUI.test_foo", |
| check_foo, expected_foo_success_count, |
| expected_foo_failure_count, |
| expected_foo_success_tracking_only_count); |
| VerifyHistogramsForFeature("InProductHelp.ShouldTriggerHelpUI.test_bar", |
| check_bar, expected_bar_success_count, |
| expected_bar_failure_count, |
| expected_bar_success_tracking_only_count); |
| VerifyHistogramsForFeature("InProductHelp.ShouldTriggerHelpUI.test_baz", |
| check_baz, expected_baz_success_count, |
| expected_baz_failure_count, |
| expected_baz_success_tracking_only_count); |
| VerifyHistogramsForFeature("InProductHelp.ShouldTriggerHelpUI.test_qux", |
| check_qux, expected_qux_success_count, |
| expected_qux_failure_count, |
| expected_qux_success_tracking_only_count); |
| |
| int expected_total_successes = |
| expected_foo_success_count + expected_bar_success_count + |
| expected_baz_success_count + expected_qux_success_count; |
| int expected_total_failures = |
| expected_foo_failure_count + expected_bar_failure_count + |
| expected_baz_failure_count + expected_qux_failure_count; |
| int expected_total_success_tracking_onlys = |
| expected_foo_success_tracking_only_count + |
| expected_bar_success_tracking_only_count + |
| expected_baz_success_tracking_only_count + |
| expected_qux_success_tracking_only_count; |
| bool should_check = check_foo || check_bar || check_baz || check_qux; |
| VerifyHistogramsForFeature("InProductHelp.ShouldTriggerHelpUI", |
| should_check, expected_total_successes, |
| expected_total_failures, |
| expected_total_success_tracking_onlys); |
| } |
| |
| void VerifyUserActionsTriggerChecks( |
| const base::UserActionTester& user_action_tester, |
| int expected_foo_count, |
| int expected_bar_count, |
| int expected_baz_count, |
| int expected_qux_count) { |
| EXPECT_EQ(expected_foo_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUI.test_foo")); |
| EXPECT_EQ(expected_bar_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUI.test_bar")); |
| EXPECT_EQ(expected_baz_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUI.test_baz")); |
| EXPECT_EQ(expected_qux_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUI.test_qux")); |
| } |
| |
| void VerifyUserActionsTriggered( |
| const base::UserActionTester& user_action_tester, |
| int expected_foo_count, |
| int expected_bar_count, |
| int expected_baz_count, |
| int expected_qux_count) { |
| EXPECT_EQ( |
| expected_foo_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.Triggered.test_foo")); |
| EXPECT_EQ( |
| expected_bar_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.Triggered.test_bar")); |
| EXPECT_EQ( |
| expected_baz_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.Triggered.test_baz")); |
| EXPECT_EQ( |
| expected_qux_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.Triggered.test_qux")); |
| } |
| |
| void VerifyUserActionsNotTriggered( |
| const base::UserActionTester& user_action_tester, |
| int expected_foo_count, |
| int expected_bar_count, |
| int expected_baz_count, |
| int expected_qux_count) { |
| EXPECT_EQ( |
| expected_foo_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.NotTriggered.test_foo")); |
| EXPECT_EQ( |
| expected_bar_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.NotTriggered.test_bar")); |
| EXPECT_EQ( |
| expected_baz_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.NotTriggered.test_baz")); |
| EXPECT_EQ( |
| expected_qux_count, |
| user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult.NotTriggered.test_qux")); |
| } |
| |
| void VerifyUserActionsWouldHaveTriggered( |
| const base::UserActionTester& user_action_tester, |
| int expected_foo_count, |
| int expected_bar_count, |
| int expected_baz_count, |
| int expected_qux_count) { |
| EXPECT_EQ(expected_foo_count, user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult." |
| "WouldHaveTriggered.test_foo")); |
| EXPECT_EQ(expected_bar_count, user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult." |
| "WouldHaveTriggered.test_bar")); |
| EXPECT_EQ(expected_baz_count, user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult." |
| "WouldHaveTriggered.test_baz")); |
| EXPECT_EQ(expected_qux_count, user_action_tester.GetActionCount( |
| "InProductHelp.ShouldTriggerHelpUIResult." |
| "WouldHaveTriggered.test_qux")); |
| } |
| |
| void VerifyUserActionsDismissed( |
| const base::UserActionTester& user_action_tester, |
| int expected_dismissed_count) { |
| EXPECT_EQ(expected_dismissed_count, |
| user_action_tester.GetActionCount("InProductHelp.Dismissed")); |
| } |
| |
| protected: |
| virtual std::unique_ptr<TestTrackerInMemoryEventStore> CreateEventStore() { |
| // Returns a EventStore that will successfully initialize. |
| return std::make_unique<TestTrackerInMemoryEventStore>(true); |
| } |
| |
| virtual std::unique_ptr<ConditionValidator> CreateConditionValidator() { |
| return std::make_unique<OnceConditionValidator>(); |
| } |
| |
| virtual bool ShouldAvailabilityStoreBeReady() { return true; } |
| |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| std::unique_ptr<TrackerImpl> tracker_; |
| raw_ptr<TestTrackerInMemoryEventStore> event_store_; |
| raw_ptr<TestTrackerAvailabilityModel> availability_model_; |
| raw_ptr<TestTrackerDisplayLockController> display_lock_controller_; |
| raw_ptr<EditableConfiguration> configuration_; |
| raw_ptr<TestTrackerEventExporter> event_exporter_; |
| raw_ptr<TestSessionController> session_controller_; |
| base::HistogramTester histogram_tester_; |
| raw_ptr<ConditionValidator> condition_validator_; |
| raw_ptr<TestTimeProvider> time_provider_; |
| }; |
| |
| // A top-level test class where the store fails to initialize. |
| class FailingStoreInitTrackerImplTest : public TrackerImplTest { |
| public: |
| FailingStoreInitTrackerImplTest() = default; |
| |
| FailingStoreInitTrackerImplTest(const FailingStoreInitTrackerImplTest&) = |
| delete; |
| FailingStoreInitTrackerImplTest& operator=( |
| const FailingStoreInitTrackerImplTest&) = delete; |
| |
| protected: |
| std::unique_ptr<TestTrackerInMemoryEventStore> CreateEventStore() override { |
| // Returns a EventStore that will fail to initialize. |
| return std::make_unique<TestTrackerInMemoryEventStore>(false); |
| } |
| }; |
| |
| // A top-level test class where the AvailabilityModel fails to initialize. |
| class FailingAvailabilityModelInitTrackerImplTest : public TrackerImplTest { |
| public: |
| FailingAvailabilityModelInitTrackerImplTest() = default; |
| |
| FailingAvailabilityModelInitTrackerImplTest( |
| const FailingAvailabilityModelInitTrackerImplTest&) = delete; |
| FailingAvailabilityModelInitTrackerImplTest& operator=( |
| const FailingAvailabilityModelInitTrackerImplTest&) = delete; |
| |
| protected: |
| bool ShouldAvailabilityStoreBeReady() override { return false; } |
| }; |
| |
| } // namespace |
| |
| TEST_F(TrackerImplTest, TestCreateTestTracker) { |
| EXPECT_NE(feature_engagement::CreateTestTracker(), nullptr); |
| } |
| |
| TEST_F(TrackerImplTest, TestInitialization) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_TRUE(callback.success()); |
| } |
| |
| TEST_F(TrackerImplTest, TestInitializationMultipleCallbacks) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback1; |
| StoringInitializedCallback callback2; |
| |
| tracker_->AddOnInitializedCallback( |
| base::BindOnce(&StoringInitializedCallback::OnInitialized, |
| base::Unretained(&callback1))); |
| tracker_->AddOnInitializedCallback( |
| base::BindOnce(&StoringInitializedCallback::OnInitialized, |
| base::Unretained(&callback2))); |
| EXPECT_FALSE(callback1.invoked()); |
| EXPECT_FALSE(callback2.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback1.invoked()); |
| EXPECT_TRUE(callback2.invoked()); |
| EXPECT_TRUE(callback1.success()); |
| EXPECT_TRUE(callback2.success()); |
| } |
| |
| TEST_F(TrackerImplTest, TestAddingCallbackAfterInitFinished) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(callback.invoked()); |
| } |
| |
| TEST_F(TrackerImplTest, TestAddingCallbackBeforeAndAfterInitFinished) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback_before; |
| tracker_->AddOnInitializedCallback( |
| base::BindOnce(&StoringInitializedCallback::OnInitialized, |
| base::Unretained(&callback_before))); |
| EXPECT_FALSE(callback_before.invoked()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(callback_before.invoked()); |
| |
| StoringInitializedCallback callback_after; |
| tracker_->AddOnInitializedCallback( |
| base::BindOnce(&StoringInitializedCallback::OnInitialized, |
| base::Unretained(&callback_after))); |
| EXPECT_FALSE(callback_after.invoked()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(callback_after.invoked()); |
| } |
| |
| TEST_F(FailingStoreInitTrackerImplTest, TestFailingInitialization) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_FALSE(callback.success()); |
| } |
| |
| TEST_F(FailingStoreInitTrackerImplTest, |
| TestFailingInitializationMultipleCallbacks) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback1; |
| StoringInitializedCallback callback2; |
| tracker_->AddOnInitializedCallback( |
| base::BindOnce(&StoringInitializedCallback::OnInitialized, |
| base::Unretained(&callback1))); |
| tracker_->AddOnInitializedCallback( |
| base::BindOnce(&StoringInitializedCallback::OnInitialized, |
| base::Unretained(&callback2))); |
| EXPECT_FALSE(callback1.invoked()); |
| EXPECT_FALSE(callback2.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback1.invoked()); |
| EXPECT_TRUE(callback2.invoked()); |
| EXPECT_FALSE(callback1.success()); |
| EXPECT_FALSE(callback2.success()); |
| } |
| |
| TEST_F(FailingAvailabilityModelInitTrackerImplTest, AvailabilityModelNotReady) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_FALSE(callback.success()); |
| } |
| |
| TEST_F(TrackerImplTest, TestMigrateEvents) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| TestTrackerEventExporter::EventData event1("test", 1); |
| event_exporter_->SetEventsToExport({event1}); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_TRUE(callback.success()); |
| |
| // Check that event made it into the store. |
| Event stored_event1 = event_store_->GetEvent("test"); |
| EXPECT_EQ("test", stored_event1.name()); |
| ASSERT_EQ(1, stored_event1.events_size()); |
| EXPECT_EQ(1u, stored_event1.events(0).day()); |
| EXPECT_EQ(1u, stored_event1.events(0).count()); |
| } |
| |
| TEST_F(TrackerImplTest, TestMigrateMultipleEvents) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| TestTrackerEventExporter::EventData event1("test", 1); |
| TestTrackerEventExporter::EventData event2("test2", 1); |
| event_exporter_->SetEventsToExport({event1, event2}); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_TRUE(callback.success()); |
| |
| // Check that events made it into the store. |
| Event stored_event1 = event_store_->GetEvent("test"); |
| EXPECT_EQ("test", stored_event1.name()); |
| ASSERT_EQ(1, stored_event1.events_size()); |
| EXPECT_EQ(1u, stored_event1.events(0).day()); |
| EXPECT_EQ(1u, stored_event1.events(0).count()); |
| |
| Event stored_event2 = event_store_->GetEvent("test2"); |
| EXPECT_EQ("test2", stored_event2.name()); |
| ASSERT_EQ(1, stored_event2.events_size()); |
| EXPECT_EQ(1u, stored_event2.events(0).day()); |
| EXPECT_EQ(1u, stored_event2.events(0).count()); |
| } |
| |
| TEST_F(TrackerImplTest, TestMigrateSameEventMultipleTimes) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| TestTrackerEventExporter::EventData event1("test", 1); |
| TestTrackerEventExporter::EventData event2("test", 1); |
| TestTrackerEventExporter::EventData event3("test", 2); |
| event_exporter_->SetEventsToExport({event1, event2, event3}); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_TRUE(callback.success()); |
| |
| // Check that events made it into the store. |
| Event stored_event = event_store_->GetEvent("test"); |
| EXPECT_EQ("test", stored_event.name()); |
| ASSERT_EQ(2, stored_event.events_size()); |
| EXPECT_EQ(1u, stored_event.events(0).day()); |
| EXPECT_EQ(2u, stored_event.events(0).count()); |
| |
| EXPECT_EQ(2u, stored_event.events(1).day()); |
| EXPECT_EQ(1u, stored_event.events(1).count()); |
| } |
| |
| TEST_F(TrackerImplTest, TestNoMigration) { |
| std::unique_ptr<Tracker> tracker = feature_engagement::CreateTestTracker(); |
| EXPECT_FALSE(tracker->IsInitialized()); |
| |
| StoringInitializedCallback callback; |
| tracker->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished and no crash or NPE happens. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_TRUE(callback.success()); |
| } |
| |
| TEST_F(TrackerImplTest, TestSetPriorityNotificationBeforeRegistration) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| |
| bool invoked = false; |
| |
| // Set priority notification, and then register handler. IPH will show up |
| // immediately after registration. |
| tracker_->SetPriorityNotification(kTrackerTestFeatureFoo); |
| tracker_->RegisterPriorityNotificationHandler( |
| kTrackerTestFeatureFoo, |
| base::BindLambdaForTesting([&]() { invoked = true; })); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(invoked); |
| |
| // Try registering handler once again. The IPH won't show up again since the |
| // notification has been consumed. |
| invoked = false; |
| tracker_->RegisterPriorityNotificationHandler( |
| kTrackerTestFeatureFoo, |
| base::BindLambdaForTesting([&]() { invoked = true; })); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(invoked); |
| |
| // Set priority notification one more time. Now the IPH will show up. |
| tracker_->SetPriorityNotification(kTrackerTestFeatureFoo); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(invoked); |
| } |
| |
| TEST_F(TrackerImplTest, TestSetPriorityNotificationAfterRegistration) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| |
| bool invoked = false; |
| |
| // Register the handler first, and then set priority notification. |
| tracker_->RegisterPriorityNotificationHandler( |
| kTrackerTestFeatureFoo, |
| base::BindLambdaForTesting([&]() { invoked = true; })); |
| tracker_->SetPriorityNotification(kTrackerTestFeatureFoo); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(invoked); |
| |
| // Set priority notification again. The IPH won't show up again, since the |
| // handler is good for only one use. |
| invoked = false; |
| tracker_->SetPriorityNotification(kTrackerTestFeatureFoo); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(invoked); |
| } |
| |
| TEST_F(TrackerImplTest, TestUnregisterPriorityNotification) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| |
| bool invoked = false; |
| |
| // Register the handler, and unregister before setting the notification. The |
| // IPH won't show up. |
| invoked = false; |
| tracker_->RegisterPriorityNotificationHandler( |
| kTrackerTestFeatureFoo, |
| base::BindLambdaForTesting([&]() { invoked = true; })); |
| tracker_->UnregisterPriorityNotificationHandler(kTrackerTestFeatureFoo); |
| tracker_->SetPriorityNotification(kTrackerTestFeatureFoo); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(invoked); |
| } |
| |
| TEST_F(TrackerImplTest, TestTriggering) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // The first time a feature triggers it should be shown. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 2, 0, 0, 1); |
| VerifyUserActionsTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 1, 0, 0, 1); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 0); |
| VerifyHistograms(true, 1, 1, 0, false, 0, 0, 0, false, 0, 0, 0, true, 0, 1, |
| 0); |
| |
| // While in-product help is currently showing, no other features should be |
| // shown. |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 0); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 2, 1, 0, 2); |
| VerifyUserActionsTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 1, 1, 0, 2); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 0); |
| VerifyHistograms(true, 1, 1, 0, true, 0, 1, 0, false, 0, 0, 0, true, 0, 2, 0); |
| |
| // After dismissing the current in-product help, that feature can not be shown |
| // again, but a different feature should. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 2u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 2u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 3, 2, 0, 3); |
| VerifyUserActionsTriggered(user_action_tester, 1, 1, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 2, 1, 0, 3); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 1); |
| VerifyHistograms(true, 1, 2, 0, true, 1, 1, 0, false, 0, 0, 0, true, 0, 3, 0); |
| |
| // After dismissing the second registered feature, no more in-product help |
| // should be shown, since kTrackerTestFeatureQux is invalid. |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 2u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 2u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 2u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 4, 3, 0, 4); |
| VerifyUserActionsTriggered(user_action_tester, 1, 1, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 3, 2, 0, 4); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 2); |
| VerifyHistograms(true, 1, 3, 0, true, 1, 2, 0, false, 0, 0, 0, true, 0, 4, 0); |
| } |
| |
| TEST_F(TrackerImplTest, TestTriggeringWithSessionController) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // The first time a feature triggers it should be shown. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 1u); |
| |
| // Dismiss the feature. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| |
| // Make the next `ShouldTriggerHelpUI` call trigger the session reset. |
| session_controller_->SetShouldResetForNextCall(true); |
| |
| // The same feature can be shown again, and blocks a different feature. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 2u); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 2u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 0); |
| VerifyGroupEventTriggerEvents(kTrackerTestGroupOne, 2u); |
| } |
| |
| TEST_F(TrackerImplTest, TestTrackingOnlyTriggering) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // When another feature is showing, tracking only features should not trigger. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBaz)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBaz, 0u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 1, 0, 1, 0); |
| VerifyUserActionsTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 0, 0, 1, 0); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 0); |
| VerifyHistograms(true, 1, 0, 0, false, 0, 0, 0, true, 0, 1, 0, false, 0, 0, |
| 0); |
| |
| // Now verify tracking only kTrackerTestFeatureBaz would have triggered and is |
| // immediately be dismissed. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| VerifyUserActionsDismissed(user_action_tester, 1); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBaz)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBaz, 1u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 1, 0, 2, 0); |
| VerifyUserActionsTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 0, 0, 1, 0); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 1, 0); |
| VerifyUserActionsDismissed(user_action_tester, 2); |
| VerifyHistograms(true, 1, 0, 0, false, 0, 0, 0, true, 0, 1, 1, false, 0, 0, |
| 0); |
| |
| // Other in-product help is should be showable after a tracking only feature |
| // would have been triggered, because nothing is currently showing. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 1u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 1, 1, 2, 0); |
| VerifyUserActionsTriggered(user_action_tester, 1, 1, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 0, 0, 1, 0); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 1, 0); |
| VerifyUserActionsDismissed(user_action_tester, 2); |
| VerifyHistograms(true, 1, 0, 0, true, 1, 0, 0, true, 0, 1, 1, false, 0, 0, 0); |
| } |
| |
| TEST_F(TrackerImplTest, TestHasEverTriggered) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // All the features should not be triggered yet. |
| EXPECT_FALSE(tracker_->HasEverTriggered(kTrackerTestFeatureFoo, false)); |
| EXPECT_FALSE(tracker_->HasEverTriggered(kTrackerTestFeatureBar, false)); |
| EXPECT_FALSE(tracker_->HasEverTriggered(kTrackerTestFeatureBaz, false)); |
| EXPECT_FALSE(tracker_->HasEverTriggered(kTrackerTestFeatureQux, false)); |
| |
| // For triggered features, has ever triggered from storage should returns |
| // true, as the storage is set to 1. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 0u); |
| EXPECT_TRUE(tracker_->HasEverTriggered(kTrackerTestFeatureFoo, false)); |
| EXPECT_FALSE(tracker_->HasEverTriggered(kTrackerTestFeatureBar, false)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| |
| // For tracking only feature, the event will still get recorded even |
| // ShouldTriggerHelpUI returns false. |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBaz)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBaz, 1u); |
| EXPECT_TRUE(tracker_->HasEverTriggered(kTrackerTestFeatureBaz, false)); |
| |
| // If |from_window| = true, HasEverTriggered will always returns false as |
| // window size is 0 in test configurations. |
| EXPECT_FALSE(tracker_->HasEverTriggered(kTrackerTestFeatureFoo, true)); |
| EXPECT_FALSE(tracker_->HasEverTriggered(kTrackerTestFeatureBaz, true)); |
| } |
| |
| TEST_F(TrackerImplTest, TestWouldTriggerInspection) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // Initially, both foo and bar would have been shown. |
| EXPECT_TRUE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| EXPECT_TRUE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_FALSE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 0u); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 0u); |
| VerifyEventTriggerEvents(kTrackerTestFeatureQux, 0u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 0); |
| VerifyHistograms(false, 0, 0, 0, false, 0, 0, 0, false, 0, 0, 0, false, 0, 0, |
| 0); |
| |
| // While foo shows, nothing else would have been shown. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_FALSE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1); |
| VerifyUserActionsTriggerChecks(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 0); |
| VerifyHistograms(true, 1, 0, 0, false, 0, 0, 0, false, 0, 0, 0, false, 0, 0, |
| 0); |
| |
| // After foo has been dismissed, it would not have triggered again, but bar |
| // would have. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| EXPECT_FALSE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| EXPECT_TRUE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_FALSE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 1); |
| VerifyUserActionsTriggerChecks(user_action_tester, 2, 1, 0, 0); |
| VerifyUserActionsTriggered(user_action_tester, 1, 1, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 1); |
| VerifyHistograms(true, 1, 1, 0, true, 1, 0, 0, false, 0, 0, 0, false, 0, 0, |
| 0); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| TEST_F(TrackerImplTest, TestWouldTriggerWithUpdatedConfig) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // Initially, foo would have been shown. |
| EXPECT_TRUE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| |
| FeatureConfig config; |
| config.valid = false; |
| config.used.name = kTrackerTestFeatureFoo.name + std::string("_used"); |
| config.trigger.name = kTrackerTestFeatureFoo.name + std::string("_trigger"); |
| |
| auto provider = std::make_unique<TestConfigurationProvider>(); |
| provider->SetConfig(config); |
| tracker_->UpdateConfig(kTrackerTestFeatureFoo, provider.get()); |
| EXPECT_FALSE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| |
| config.valid = true; |
| provider->SetConfig(config); |
| tracker_->UpdateConfig(kTrackerTestFeatureFoo, provider.get()); |
| EXPECT_TRUE(tracker_->WouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| } |
| #endif |
| |
| TEST_F(TrackerImplTest, TestTriggerStateInspection) { |
| // Before initialization has finished, NOT_READY should always be returned. |
| EXPECT_EQ(Tracker::TriggerState::NOT_READY, |
| tracker_->GetTriggerState(kTrackerTestFeatureFoo)); |
| EXPECT_EQ(Tracker::TriggerState::NOT_READY, |
| tracker_->GetTriggerState(kTrackerTestFeatureQux)); |
| |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| EXPECT_EQ(Tracker::TriggerState::HAS_NOT_BEEN_DISPLAYED, |
| tracker_->GetTriggerState(kTrackerTestFeatureFoo)); |
| EXPECT_EQ(Tracker::TriggerState::HAS_NOT_BEEN_DISPLAYED, |
| tracker_->GetTriggerState(kTrackerTestFeatureBar)); |
| |
| // The first time a feature triggers it should be shown. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| EXPECT_EQ(Tracker::TriggerState::HAS_BEEN_DISPLAYED, |
| tracker_->GetTriggerState(kTrackerTestFeatureFoo)); |
| |
| // Trying to show again should keep state as displayed. |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureFoo, 1u); |
| EXPECT_EQ(Tracker::TriggerState::HAS_BEEN_DISPLAYED, |
| tracker_->GetTriggerState(kTrackerTestFeatureFoo)); |
| |
| // Other features should also be kept at not having been displayed. |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 0); |
| EXPECT_EQ(Tracker::TriggerState::HAS_NOT_BEEN_DISPLAYED, |
| tracker_->GetTriggerState(kTrackerTestFeatureBar)); |
| |
| // Dismiss foo and show qux, which should update TriggerState of bar, and keep |
| // TriggerState for foo. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(kTrackerTestFeatureBar, 1); |
| EXPECT_EQ(Tracker::TriggerState::HAS_BEEN_DISPLAYED, |
| tracker_->GetTriggerState(kTrackerTestFeatureFoo)); |
| EXPECT_EQ(Tracker::TriggerState::HAS_BEEN_DISPLAYED, |
| tracker_->GetTriggerState(kTrackerTestFeatureBar)); |
| } |
| |
| TEST_F(TrackerImplTest, TestNotifyEvent) { |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| tracker_->NotifyEvent("foo"); |
| tracker_->NotifyEvent("foo"); |
| tracker_->NotifyEvent("bar"); |
| tracker_->NotifyEvent(kTrackerTestFeatureFoo.name + std::string("_used")); |
| tracker_->NotifyEvent(kTrackerTestFeatureFoo.name + std::string("_trigger")); |
| |
| // Used event will record both NotifyEvent and NotifyUsedEvent. Explicitly |
| // specify the whole user action string here. |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyUsedEvent.test_foo")); |
| EXPECT_EQ(2, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyEvent.test_foo")); |
| EXPECT_EQ(0, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyUsedEvent.test_bar")); |
| EXPECT_EQ(0, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyEvent.test_bar")); |
| |
| Event foo_event = event_store_->GetEvent("foo"); |
| ASSERT_EQ(1, foo_event.events_size()); |
| EXPECT_EQ(1u, foo_event.events(0).day()); |
| EXPECT_EQ(2u, foo_event.events(0).count()); |
| |
| Event bar_event = event_store_->GetEvent("bar"); |
| ASSERT_EQ(1, bar_event.events_size()); |
| EXPECT_EQ(1u, bar_event.events(0).day()); |
| EXPECT_EQ(1u, bar_event.events(0).count()); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| |
| TEST_F(TrackerImplTest, TestNotifyUsedEvent) { |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| tracker_->NotifyUsedEvent(kTrackerTestFeatureFoo); |
| |
| // Used event will record both NotifyEvent and NotifyUsedEvent. Explicitly |
| // specify the whole user action string here. |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyUsedEvent.test_foo")); |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyEvent.test_foo")); |
| EXPECT_EQ(0, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyUsedEvent.test_bar")); |
| EXPECT_EQ(0, user_action_tester.GetActionCount( |
| "InProductHelp.NotifyEvent.test_bar")); |
| |
| Event event = event_store_->GetEvent("test_foo_used"); |
| EXPECT_EQ(1, event.events_size()); |
| } |
| |
| TEST_F(TrackerImplTest, TestClearEventData) { |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| tracker_->NotifyUsedEvent(kTrackerTestFeatureFoo); |
| tracker_->NotifyUsedEvent(kTrackerTestFeatureBaz); |
| tracker_->ClearEventData(kTrackerTestFeatureFoo); |
| |
| // Test clearing used events. |
| Event event = event_store_->GetEvent("test_foo_used"); |
| EXPECT_EQ(0, event.events_size()); |
| event = event_store_->GetEvent("test_baz_used"); |
| EXPECT_EQ(1, event.events_size()); |
| |
| // Test clearing other events. |
| tracker_->NotifyEvent("test_event_event"); |
| EXPECT_EQ(1, event_store_->GetEvent("test_event_event").events_size()); |
| tracker_->ClearEventData(kTrackerTestFeatureEvent); |
| EXPECT_EQ(0, event_store_->GetEvent("test_event_event").events_size()); |
| } |
| |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| TEST_F(TrackerImplTest, ShouldPassThroughAcquireDisplayLock) { |
| auto lock_handle = std::make_unique<DisplayLockHandle>(base::DoNothing()); |
| DisplayLockHandle* lock_handle_ptr = lock_handle.get(); |
| display_lock_controller_->SetNextDisplayLockHandle(std::move(lock_handle)); |
| EXPECT_EQ(lock_handle_ptr, tracker_->AcquireDisplayLock().get()); |
| } |
| |
| // Checks that the time is correctly logged when an IPH is presented. |
| TEST_F(TrackerImplTest, ShownTimeLogged) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| const char histogram_name[] = "InProductHelp.ShownTime.test_foo"; |
| |
| base::Time now = base::Time::Now(); |
| time_provider_->SetCurrentTime(now); |
| |
| // Start the timer by allowing the IPH. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| histogram_tester_.ExpectTotalCount(histogram_name, 0); |
| |
| // Fake running the clock, where the IPH is displayed. |
| time_provider_->SetCurrentTime(now + base::Seconds(3)); |
| |
| // Dismiss the IPH and assert that the ShownTime is correctly logged. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| histogram_tester_.ExpectTotalCount(histogram_name, 1); |
| histogram_tester_.ExpectUniqueTimeSample(histogram_name, base::Seconds(3), 1); |
| } |
| |
| // Checks that the time is not logged when the feature is `tracking_only`. |
| TEST_F(TrackerImplTest, TrackingOnly_ShownTimeNotLogged) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| const char histogram_name[] = "InProductHelp.ShownTime.test_baz"; |
| |
| base::Time now = base::Time::Now(); |
| time_provider_->SetCurrentTime(now); |
| |
| // Start the timer by allowing the IPH. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| histogram_tester_.ExpectTotalCount(histogram_name, 0); |
| |
| // Fake running the clock, where the IPH is displayed. |
| time_provider_->SetCurrentTime(now + base::Seconds(3)); |
| |
| // Dismiss the IPH and assert that the ShownTime is not logged. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| histogram_tester_.ExpectTotalCount(histogram_name, 0); |
| } |
| |
| // Base class for any tests that specifically require a |
| // |OnceConditionValidator|. |
| class OnceConditionTrackerImplTest : public TrackerImplTest { |
| public: |
| OnceConditionTrackerImplTest() = default; |
| ~OnceConditionTrackerImplTest() override = default; |
| |
| protected: |
| std::unique_ptr<ConditionValidator> CreateConditionValidator() override { |
| auto once_condition_validator = std::make_unique<OnceConditionValidator>(); |
| once_condition_validator_ = once_condition_validator.get(); |
| return once_condition_validator; |
| } |
| raw_ptr<OnceConditionValidator> once_condition_validator_; |
| }; |
| |
| // Checks that the times are logged even when multiple IPH are presented. |
| TEST_F(OnceConditionTrackerImplTest, MultipleShownTimeLogged) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| const char histogram_name_foo[] = "InProductHelp.ShownTime.test_foo"; |
| const char histogram_name_bar[] = "InProductHelp.ShownTime.test_bar"; |
| |
| once_condition_validator_->AllowMultipleFeaturesForTesting(true); |
| |
| base::Time start = base::Time::Now(); |
| time_provider_->SetCurrentTime(start); |
| |
| // Start the timer by allowing a first IPH. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| histogram_tester_.ExpectTotalCount(histogram_name_foo, 0); |
| histogram_tester_.ExpectTotalCount(histogram_name_bar, 0); |
| |
| // Fake running the clock, where the first IPH is displayed. |
| time_provider_->SetCurrentTime(start + base::Seconds(1)); |
| |
| // Start a second timer by allowing a second IPH. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| histogram_tester_.ExpectTotalCount(histogram_name_foo, 0); |
| histogram_tester_.ExpectTotalCount(histogram_name_bar, 0); |
| |
| // Fake running the clock while both are presented. |
| time_provider_->SetCurrentTime(start + base::Seconds(2)); |
| |
| // Dismiss the first IPH and assert that the ShownTime is correctly logged. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| histogram_tester_.ExpectTotalCount(histogram_name_foo, 1); |
| histogram_tester_.ExpectTotalCount(histogram_name_bar, 0); |
| histogram_tester_.ExpectUniqueTimeSample(histogram_name_foo, base::Seconds(2), |
| 1); |
| |
| // Fake running the clock, where the first IPH is displayed. |
| time_provider_->SetCurrentTime(start + base::Seconds(4)); |
| |
| // Dismiss the second IPH and assert that the ShownTime is correctly logged. |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| histogram_tester_.ExpectTotalCount(histogram_name_foo, 1); |
| histogram_tester_.ExpectTotalCount(histogram_name_bar, 1); |
| histogram_tester_.ExpectUniqueTimeSample(histogram_name_foo, base::Seconds(2), |
| 1); |
| histogram_tester_.ExpectUniqueTimeSample(histogram_name_bar, base::Seconds(3), |
| 1); |
| } |
| |
| class MultipleEventModelTrackerImplTest : public TrackerImplTest { |
| public: |
| MultipleEventModelTrackerImplTest() = default; |
| |
| MultipleEventModelTrackerImplTest(const MultipleEventModelTrackerImplTest&) = |
| delete; |
| MultipleEventModelTrackerImplTest& operator=( |
| const MultipleEventModelTrackerImplTest&) = delete; |
| |
| void SetUp() override { |
| std::unique_ptr<EditableConfiguration> configuration = |
| std::make_unique<EditableConfiguration>(); |
| configuration_ = configuration.get(); |
| |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureFoo, |
| true /* is_valid */, false /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureBar, |
| true /* is_valid */, false /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureBaz, |
| true /* is_valid */, true /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureQux, |
| false /* is_valid */, false /* tracking_only */, |
| false /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureEvent, |
| /*valid=*/true, /*tracking_only=*/false, |
| /*snooze_params=*/false, "test_event_event"); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureSnooze, |
| true /* is_valid */, false /* tracking_only */, |
| true /* snooze_params */); |
| RegisterFeatureConfig(configuration.get(), kTrackerTestFeatureDeviceStorage, |
| true /* is_valid */, false /* tracking_only */, |
| true /* snooze_params */, |
| nullptr /* additional_event_name */, |
| StorageType::DEVICE); |
| RegisterGroupConfig(configuration.get(), kTrackerTestGroupOne, |
| true /* is_valid */); |
| |
| std::unique_ptr<TestTrackerInMemoryEventStore> profile_event_store = |
| CreateEventStore(); |
| profile_event_store_ = profile_event_store.get(); |
| |
| std::unique_ptr<TestTrackerInMemoryEventStore> device_event_store = |
| CreateEventStore(); |
| device_event_store_ = device_event_store.get(); |
| |
| auto profile_event_model = std::make_unique<EventModelImpl>( |
| std::move(profile_event_store), |
| std::make_unique<StoreEverythingEventStorageValidator>()); |
| profile_event_model_ = profile_event_model.get(); |
| |
| auto device_event_model = std::make_unique<EventModelImpl>( |
| std::move(device_event_store), |
| std::make_unique<StoreEverythingEventStorageValidator>()); |
| device_event_model_ = device_event_model.get(); |
| |
| auto multiple_event_model_provider = |
| std::make_unique<MultipleEventModelProvider>( |
| std::move(profile_event_model), std::move(device_event_model)); |
| |
| auto availability_model = std::make_unique<TestTrackerAvailabilityModel>(); |
| availability_model_ = availability_model.get(); |
| availability_model_->SetIsReady(ShouldAvailabilityStoreBeReady()); |
| |
| auto display_lock_controller = |
| std::make_unique<TestTrackerDisplayLockController>(); |
| display_lock_controller_ = display_lock_controller.get(); |
| |
| auto condition_validator = CreateConditionValidator(); |
| condition_validator_ = condition_validator.get(); |
| |
| auto time_provider = std::make_unique<TestTimeProvider>(); |
| time_provider_ = time_provider.get(); |
| time_provider->SetCurrentDay(1u); |
| |
| auto event_exporter = std::make_unique<TestTrackerEventExporter>(); |
| event_exporter_ = event_exporter.get(); |
| |
| auto session_controller = std::make_unique<TestSessionController>(); |
| session_controller_ = session_controller.get(); |
| |
| tracker_ = std::make_unique<TrackerImpl>( |
| std::move(multiple_event_model_provider), std::move(availability_model), |
| std::move(configuration), std::move(display_lock_controller), |
| std::move(condition_validator), std::move(time_provider), |
| std::move(event_exporter), std::move(session_controller), |
| nullptr /* event_storage_migration */, nullptr /* pref_service */); |
| } |
| |
| protected: |
| void VerifyEventTriggerForStore(TestTrackerInMemoryEventStore* store, |
| const std::string& event_name, |
| uint32_t count) { |
| Event trigger_event = store->GetEvent(event_name); |
| if (count == 0) { |
| EXPECT_EQ(0, trigger_event.events_size()); |
| return; |
| } |
| |
| EXPECT_EQ(1, trigger_event.events_size()); |
| EXPECT_EQ(1u, trigger_event.events(0).day()); |
| EXPECT_EQ(count, trigger_event.events(0).count()); |
| } |
| |
| void VerifyEventTrigger(TestTrackerInMemoryEventStore* store, |
| std::string event_name, |
| uint32_t count) { |
| VerifyEventTriggerForStore(store, event_name, count); |
| } |
| |
| void VerifyEventTriggerEvents(TestTrackerInMemoryEventStore* store, |
| const base::Feature& feature, |
| uint32_t count) { |
| VerifyEventTrigger( |
| store, configuration_->GetFeatureConfig(feature).trigger.name, count); |
| } |
| |
| void VerifyGroupEventTriggerEvents(TestTrackerInMemoryEventStore* store, |
| const base::Feature& group, |
| uint32_t count) { |
| VerifyEventTrigger( |
| store, configuration_->GetGroupConfig(group).trigger.name, count); |
| } |
| |
| raw_ptr<EventModel> profile_event_model_; |
| raw_ptr<EventModel> device_event_model_; |
| raw_ptr<TestTrackerInMemoryEventStore> device_event_store_; |
| raw_ptr<TestTrackerInMemoryEventStore> profile_event_store_; |
| }; |
| |
| TEST_F(MultipleEventModelTrackerImplTest, |
| TestInitializationWithMultipleStorage) { |
| EXPECT_FALSE(tracker_->IsInitialized()); |
| |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| EXPECT_FALSE(callback.invoked()); |
| |
| // Ensure all initialization is finished. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(tracker_->IsInitialized()); |
| EXPECT_TRUE(callback.invoked()); |
| EXPECT_TRUE(callback.success()); |
| } |
| |
| TEST_F(MultipleEventModelTrackerImplTest, TestWriteEventToMultipleStores) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // The first time a feature triggers it should be shown. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 2, 0, 0, 1); |
| VerifyUserActionsTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 1, 0, 0, 1); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 0); |
| VerifyHistograms(true, 1, 1, 0, false, 0, 0, 0, false, 0, 0, 0, true, 0, 1, |
| 0); |
| |
| // While in-product help is currently showing, no other features should be |
| // shown. |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureBar, 0); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureBar, 0); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 1u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 2, 1, 0, 2); |
| VerifyUserActionsTriggered(user_action_tester, 1, 0, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 1, 1, 0, 2); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 0); |
| VerifyHistograms(true, 1, 1, 0, true, 0, 1, 0, false, 0, 0, 0, true, 0, 2, 0); |
| |
| // After dismissing the current in-product help, that feature can not be shown |
| // again, but a different feature should. |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 1u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 1u); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureBar, 1u); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureBar, 1u); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 2u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 2u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 2u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 2u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 3, 2, 0, 3); |
| VerifyUserActionsTriggered(user_action_tester, 1, 1, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 2, 1, 0, 3); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 1); |
| VerifyHistograms(true, 1, 2, 0, true, 1, 1, 0, false, 0, 0, 0, true, 0, 3, 0); |
| |
| // After dismissing the second registered feature, no more in-product help |
| // should be shown, since kTrackerTestFeatureQux is invalid. |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureFoo, 1u); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 2u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 2u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureBar, 1u); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureBar, 1u); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 2u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 2u); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureQux)); |
| VerifyEventTriggerEvents(profile_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyEventTriggerEvents(device_event_store_, kTrackerTestFeatureQux, 0); |
| VerifyGroupEventTriggerEvents(profile_event_store_, kTrackerTestGroupOne, 2u); |
| VerifyGroupEventTriggerEvents(device_event_store_, kTrackerTestGroupOne, 2u); |
| VerifyUserActionsTriggerChecks(user_action_tester, 4, 3, 0, 4); |
| VerifyUserActionsTriggered(user_action_tester, 1, 1, 0, 0); |
| VerifyUserActionsNotTriggered(user_action_tester, 3, 2, 0, 4); |
| VerifyUserActionsWouldHaveTriggered(user_action_tester, 0, 0, 0, 0); |
| VerifyUserActionsDismissed(user_action_tester, 2); |
| VerifyHistograms(true, 1, 3, 0, true, 1, 2, 0, false, 0, 0, 0, true, 0, 4, 0); |
| } |
| |
| TEST_F(MultipleEventModelTrackerImplTest, TestReadEventFromSingleStorage) { |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // All the features should not be triggered yet. |
| EXPECT_FALSE( |
| tracker_->HasEverTriggered(kTrackerTestFeatureDeviceStorage, false)); |
| |
| // For triggered features, has ever triggered from storage should returns |
| // true, as the storage is set to 1. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureDeviceStorage)); |
| tracker_->Dismissed(kTrackerTestFeatureDeviceStorage); |
| VerifyEventTriggerEvents(profile_event_store_, |
| kTrackerTestFeatureDeviceStorage, 1u); |
| VerifyEventTriggerEvents(device_event_store_, |
| kTrackerTestFeatureDeviceStorage, 1u); |
| |
| const std::string& event_trigger_name = |
| kTrackerTestFeatureDeviceStorage.name + std::string("_trigger"); |
| |
| // Clear the trigger event from the profile model. `HasEverTriggered` should |
| // still return true because the event persists in the device model, which is |
| // the designated storage for this feature. |
| profile_event_model_->ClearEvent(event_trigger_name); |
| EXPECT_TRUE( |
| tracker_->HasEverTriggered(kTrackerTestFeatureDeviceStorage, false)); |
| |
| // Clear the trigger event from the device model. Now that the event is |
| // cleared from its designated storage, `HasEverTriggered` should return |
| // false. |
| device_event_model_->ClearEvent(event_trigger_name); |
| EXPECT_FALSE( |
| tracker_->HasEverTriggered(kTrackerTestFeatureDeviceStorage, false)); |
| } |
| |
| namespace test { |
| |
| class ScopedIphFeatureListTest : public TrackerImplTest { |
| public: |
| ScopedIphFeatureListTest() = default; |
| ~ScopedIphFeatureListTest() override = default; |
| |
| void SetUp() override { |
| TrackerImplTest::SetUp(); |
| |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback( |
| base::BindOnce(&StoringInitializedCallback::OnInitialized, |
| base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| }; |
| |
| TEST_F(ScopedIphFeatureListTest, InitWithNoFeaturesAllowed) { |
| ScopedIphFeatureList list; |
| list.InitWithNoFeaturesAllowed(); |
| // Init should not have enabled any features. |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitWithNoFeaturesAllowed_AllowedAfterReset) { |
| ScopedIphFeatureList list; |
| list.InitWithNoFeaturesAllowed(); |
| list.Reset(); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, |
| InitWithNoFeaturesAllowed_AllowedAfterDestruct) { |
| { |
| ScopedIphFeatureList list; |
| list.InitWithNoFeaturesAllowed(); |
| } |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitWithExistingFeatures) { |
| ScopedIphFeatureList list; |
| list.InitWithExistingFeatures({kTrackerTestFeatureFoo}); |
| // Init should not have enabled any features. |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitWithExistingFeatures_AllowedAfterReset) { |
| ScopedIphFeatureList list; |
| list.InitWithExistingFeatures({kTrackerTestFeatureFoo}); |
| list.Reset(); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, |
| InitWithExistingFeatures_AllowedAfterDestruct) { |
| { |
| ScopedIphFeatureList list; |
| list.InitWithExistingFeatures({kTrackerTestFeatureFoo}); |
| } |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitForDemo) { |
| ScopedIphFeatureList list; |
| list.InitForDemo(kTrackerTestFeatureFoo); |
| |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kIPHDemoMode)); |
| EXPECT_EQ(kTrackerTestFeatureFoo.name, |
| base::GetFieldTrialParamValueByFeature( |
| kIPHDemoMode, kIPHDemoModeFeatureChoiceParam)); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitForDemo_Reset) { |
| ScopedIphFeatureList list; |
| list.InitForDemo(kTrackerTestFeatureFoo); |
| list.Reset(); |
| |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kIPHDemoMode)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitForDemo_Destruct) { |
| { |
| ScopedIphFeatureList list; |
| list.InitForDemo(kTrackerTestFeatureFoo); |
| } |
| |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kIPHDemoMode)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitAndEnableFeatures) { |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeatures({kTrackerTestFeatureFoo, kTrackerTestFeatureBaz}); |
| |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kTrackerTestFeatureBaz)); |
| |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitAndEnableFeatures_Reset) { |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeatures({kTrackerTestFeatureFoo, kTrackerTestFeatureBaz}); |
| list.Reset(); |
| |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureBaz)); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitAndEnableFeatures_Destruct) { |
| { |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeatures( |
| {kTrackerTestFeatureFoo, kTrackerTestFeatureBaz}); |
| } |
| |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureBaz)); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitAndEnableFeaturesWithParameters) { |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeaturesWithParameters( |
| {{kTrackerTestFeatureFoo, {{"x_foo", "1"}}}, |
| {kTrackerTestFeatureBaz, {{"x_bar", "2"}, {"x_baz", "3"}}}}); |
| |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| EXPECT_EQ("1", base::GetFieldTrialParamValueByFeature(kTrackerTestFeatureFoo, |
| "x_foo")); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kTrackerTestFeatureBaz)); |
| EXPECT_EQ("2", base::GetFieldTrialParamValueByFeature(kTrackerTestFeatureBaz, |
| "x_bar")); |
| EXPECT_EQ("3", base::GetFieldTrialParamValueByFeature(kTrackerTestFeatureBaz, |
| "x_baz")); |
| |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitAndEnableFeaturesWithParameters_Reset) { |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeaturesWithParameters( |
| {{kTrackerTestFeatureFoo, {{"x_foo", "1"}}}, |
| {kTrackerTestFeatureBaz, {{"x_bar", "2"}, {"x_baz", "3"}}}}); |
| list.Reset(); |
| |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureBaz)); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, InitAndEnableFeaturesWithParameters_Destruct) { |
| { |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeaturesWithParameters( |
| {{kTrackerTestFeatureFoo, {{"x_foo", "1"}}}, |
| {kTrackerTestFeatureBaz, {{"x_bar", "2"}, {"x_baz", "3"}}}}); |
| } |
| |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureFoo)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kTrackerTestFeatureBaz)); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, NestedScopes) { |
| ScopedIphFeatureList list1; |
| ScopedIphFeatureList list2; |
| ScopedIphFeatureList list3; |
| list1.InitWithNoFeaturesAllowed(); |
| list2.InitWithExistingFeatures({kTrackerTestFeatureFoo}); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| list3.InitWithExistingFeatures({kTrackerTestFeatureBar}); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, NestedScopes_DestructInWrongOrder) { |
| ScopedIphFeatureList list1; |
| ScopedIphFeatureList list2; |
| ScopedIphFeatureList list3; |
| list1.InitWithNoFeaturesAllowed(); |
| list2.InitWithExistingFeatures({kTrackerTestFeatureFoo}); |
| list3.InitWithExistingFeatures({kTrackerTestFeatureBar}); |
| list1.Reset(); |
| list2.Reset(); |
| // Destroyed the scope that allowed Foo, but not the one that allowed Bar. |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| list3.Reset(); |
| |
| // Now there are no more scopes, so all IPH are allowed. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| |
| TEST_F(ScopedIphFeatureListTest, NestedScopes_SameFeature) { |
| ScopedIphFeatureList list1; |
| list1.InitWithExistingFeatures({kTrackerTestFeatureBar}); |
| { |
| ScopedIphFeatureList list2; |
| list2.InitWithExistingFeatures( |
| {kTrackerTestFeatureFoo, kTrackerTestFeatureBar}); |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| } |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| tracker_->Dismissed(kTrackerTestFeatureBar); |
| } |
| |
| // Test class for tests that require an actual |
| // |FeatureConfigConditionValidator|. |
| class FeatureConfigConditionValidatorTrackerTest : public TrackerImplTest { |
| public: |
| FeatureConfigConditionValidatorTrackerTest() = default; |
| ~FeatureConfigConditionValidatorTrackerTest() override = default; |
| |
| protected: |
| std::unique_ptr<ConditionValidator> CreateConditionValidator() override { |
| return std::make_unique<FeatureConfigConditionValidator>(); |
| } |
| }; |
| |
| TEST_F(FeatureConfigConditionValidatorTrackerTest, GroupRulesApplied) { |
| // Set up the group config to only allow 1 feature from its group to be |
| // displayed. |
| GroupConfig custom_group_config; |
| custom_group_config.valid = true; |
| custom_group_config.trigger.name = "custom_group_trigger"; |
| custom_group_config.trigger.comparator = Comparator(EQUAL, 0); |
| custom_group_config.trigger.window = 1u; |
| custom_group_config.trigger.storage = 1u; |
| configuration_->SetConfiguration(&kTrackerTestGroupOne, custom_group_config); |
| |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeatures( |
| {kTrackerTestFeatureFoo, kTrackerTestFeatureBar, kTrackerTestGroupOne}); |
| |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| base::UserActionTester user_action_tester; |
| |
| // The first feature should display, but the second one should not, as they |
| // share a group that only allows its features to be displayed once. |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| tracker_->Dismissed(kTrackerTestFeatureFoo); |
| EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureBar)); |
| } |
| |
| TEST_F(FeatureConfigConditionValidatorTrackerTest, TestTriggeringWithSnooze) { |
| ScopedIphFeatureList list; |
| list.InitAndEnableFeatures( |
| {kTrackerTestFeatureSnooze, kTrackerTestFeatureFoo}); |
| |
| // Ensure all initialization is finished. |
| StoringInitializedCallback callback; |
| tracker_->AddOnInitializedCallback(base::BindOnce( |
| &StoringInitializedCallback::OnInitialized, base::Unretained(&callback))); |
| base::RunLoop().RunUntilIdle(); |
| |
| base::Time now = base::Time::Now(); |
| time_provider_->SetCurrentTime(now); |
| time_provider_->SetCurrentDay(1u); |
| |
| // The first time a feature with snooze params triggers, it should be shown |
| // with snooze. |
| Tracker::TriggerDetails trigger_details = |
| tracker_->ShouldTriggerHelpUIWithSnooze(kTrackerTestFeatureSnooze); |
| EXPECT_TRUE(trigger_details.ShouldShowIph()); |
| EXPECT_TRUE(trigger_details.ShouldShowSnooze()); |
| |
| Event snooze_event = event_store_->GetEvent( |
| configuration_->GetFeatureConfig(kTrackerTestFeatureSnooze).trigger.name); |
| ASSERT_EQ(1, snooze_event.events_size()); |
| ASSERT_EQ(1u, snooze_event.events(0).day()); |
| ASSERT_EQ(1u, snooze_event.events(0).count()); |
| ASSERT_EQ(0u, snooze_event.events(0).snooze_count()); |
| |
| tracker_->DismissedWithSnooze(kTrackerTestFeatureSnooze, |
| Tracker::SnoozeAction::SNOOZED); |
| snooze_event = event_store_->GetEvent( |
| configuration_->GetFeatureConfig(kTrackerTestFeatureSnooze).trigger.name); |
| ASSERT_EQ(1u, snooze_event.events(0).snooze_count()); |
| ASSERT_EQ(now.ToDeltaSinceWindowsEpoch().InMicroseconds(), |
| snooze_event.last_snooze_time_us()); |
| ASSERT_EQ(false, snooze_event.snooze_dismissed()); |
| |
| // Now, the feature should be on snooze. |
| trigger_details = |
| tracker_->ShouldTriggerHelpUIWithSnooze(kTrackerTestFeatureSnooze); |
| EXPECT_FALSE(trigger_details.ShouldShowIph()); |
| EXPECT_FALSE(trigger_details.ShouldShowSnooze()); |
| |
| // Advance the clock so the snooze expires. Then the feature should be ready |
| // to show again. |
| time_provider_->SetCurrentTime(now + base::Days(8)); |
| trigger_details = |
| tracker_->ShouldTriggerHelpUIWithSnooze(kTrackerTestFeatureSnooze); |
| EXPECT_TRUE(trigger_details.ShouldShowIph()); |
| EXPECT_TRUE(trigger_details.ShouldShowSnooze()); |
| |
| tracker_->DismissedWithSnooze(kTrackerTestFeatureSnooze, |
| Tracker::SnoozeAction::DISMISSED); |
| snooze_event = event_store_->GetEvent( |
| configuration_->GetFeatureConfig(kTrackerTestFeatureSnooze).trigger.name); |
| ASSERT_EQ(1u, snooze_event.events(0).snooze_count()); |
| ASSERT_EQ(true, snooze_event.snooze_dismissed()); |
| |
| EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTrackerTestFeatureFoo)); |
| } |
| |
| } // namespace test |
| |
| } // namespace feature_engagement |