chromium/components/feed/core/v2/public/ios/info_card_tracker_unittest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/feed/core/v2/public/ios/info_card_tracker.h"

#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "components/feed/core/common/pref_names.h"
#import "components/feed/core/proto/v2/wire/info_card.pb.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ios_feed {
namespace {

// Test values for the tracking state.
const int info_card_type = 100;
const int multiple_trigger_count = 3;
const int view_fraction_threshold = 2;
const int minimum_seconds_between_views = 1;

class IOSInfoCardTrackerTest : public testing::Test {
 public:
  void SetUp() override {
    feed::RegisterProfilePrefs(browser_state_prefs_.registry());
  }

 protected:
  TestingPrefServiceSimple browser_state_prefs_;
};

// Test a tracking command that triggers an explicit dismissal.
TEST_F(IOSInfoCardTrackerTest, OnExplicitDismissal) {
  InfoCardTracker tracker(&browser_state_prefs_);

  // Get the info card tracking state, which should be defaulted with 0 explicit
  // dismissmals.
  feedwire::InfoCardTrackingState tracking_state =
      tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.explicitly_dismissed_count(), 0);

  // Trigger an explicit dismissal and check that the count has incremented
  // once.
  tracker.OnTrackInfoCardCommand(
      info_card_type, (int)TrackingType::kExplicitDismissal,
      view_fraction_threshold, minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.explicitly_dismissed_count(), 1);

  // Trigger multiple explicit dismissals and check that the count has
  // incremented the correct amount of times.
  for (int i = 0; i < multiple_trigger_count; ++i) {
    tracker.OnTrackInfoCardCommand(
        info_card_type, (int)TrackingType::kExplicitDismissal,
        view_fraction_threshold, minimum_seconds_between_views);
  }
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.explicitly_dismissed_count(),
            1 + multiple_trigger_count);

  // Trigger a different command and check that the explicit dismissal count
  // didn't change.
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kClick,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.explicitly_dismissed_count(),
            1 + multiple_trigger_count);
}

// Test a tracking command that triggers a view.
TEST_F(IOSInfoCardTrackerTest, OnView) {
  InfoCardTracker tracker(&browser_state_prefs_);

  // Get the info card tracking state, which should be defaulted with 0 views.
  feedwire::InfoCardTrackingState tracking_state =
      tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.view_count(), 0);

  // Trigger a view and check that the count has incremented once.
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kView,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.view_count(), 1);

  // Trigger another view immediately and check that it didn't increment since
  // it hasn't been `minimum_seconds_between_views`.
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kView,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.view_count(), 1);

  // Trigger another view after `minimum_seconds_between_views` and check that
  // the count incremented.
  base::PlatformThread::Sleep(base::Seconds(minimum_seconds_between_views));
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kView,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.view_count(), 2);

  // Trigger a different command and check that the view count didn't change.
  tracker.OnTrackInfoCardCommand(
      info_card_type, (int)TrackingType::kExplicitDismissal,
      view_fraction_threshold, minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.view_count(), 2);

  // Trigger a ReportView command after `minimum_seconds_between_views` which
  // should be handled the same way as a view, incrementing the view count.
  base::PlatformThread::Sleep(base::Seconds(minimum_seconds_between_views));
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kReportView,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.view_count(), 3);
}

// Test a tracking command that triggers a click.
TEST_F(IOSInfoCardTrackerTest, OnClick) {
  InfoCardTracker tracker(&browser_state_prefs_);

  // Get the info card tracking state, which should be defaulted with 0 clicks.
  feedwire::InfoCardTrackingState tracking_state =
      tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.click_count(), 0);

  // Trigger a click and check that the count has incremented once.
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kClick,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.click_count(), 1);

  // Trigger multiple clicks and check that the count has incremented the
  // correct amount of times.
  for (int i = 0; i < multiple_trigger_count; ++i) {
    tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kClick,
                                   view_fraction_threshold,
                                   minimum_seconds_between_views);
  }
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.click_count(), 1 + multiple_trigger_count);

  // Trigger a different command and check that the click count didn't change.
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kView,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracking_state = tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(tracking_state.click_count(), 1 + multiple_trigger_count);
}

// Test a tracking command that resets the tracking state.
TEST_F(IOSInfoCardTrackerTest, OnResetState) {
  InfoCardTracker tracker(&browser_state_prefs_);

  // Store the default tracking state to later compare after resetting the
  // state.
  feedwire::InfoCardTrackingState default_tracking_state =
      tracker.GetInfoCardTrackingStateFromPref(info_card_type);

  // Trigger a view, an explicit dismissal, and multiple clicks.
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kView,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  tracker.OnTrackInfoCardCommand(
      info_card_type, (int)TrackingType::kExplicitDismissal,
      view_fraction_threshold, minimum_seconds_between_views);
  for (int i = 0; i < multiple_trigger_count; ++i) {
    tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kClick,
                                   view_fraction_threshold,
                                   minimum_seconds_between_views);
  }

  // Check that the current tracking state reflects the commands.
  feedwire::InfoCardTrackingState current_tracking_state =
      tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(current_tracking_state.view_count(), 1);
  EXPECT_EQ(current_tracking_state.explicitly_dismissed_count(), 1);
  EXPECT_EQ(current_tracking_state.click_count(), multiple_trigger_count);

  // Trigger ResetState and check that the current tracking state has been reset
  // to the default tracking state.
  tracker.OnTrackInfoCardCommand(info_card_type, (int)TrackingType::kResetState,
                                 view_fraction_threshold,
                                 minimum_seconds_between_views);
  current_tracking_state =
      tracker.GetInfoCardTrackingStateFromPref(info_card_type);
  EXPECT_EQ(current_tracking_state.view_count(),
            default_tracking_state.view_count());
  EXPECT_EQ(current_tracking_state.explicitly_dismissed_count(),
            default_tracking_state.explicitly_dismissed_count());
  EXPECT_EQ(current_tracking_state.click_count(),
            default_tracking_state.click_count());
}

}  // namespace
}  // namespace ios_feed