chromium/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_orderer_unittest.mm

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

#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_orderer.h"

#import "base/strings/string_number_conversions.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "base/values.h"
#import "components/prefs/pref_registry_simple.h"
#import "components/prefs/testing_pref_service.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/destination_usage_history/constants.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_action_provider.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_constants.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_swift.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

namespace {

// The number of destinations immediately visible in the carousel when the
// overflow menu is opened.
//
// For the purposes of these unit tests, this value is
// statically declared below. In practice, this value is dynamically calculated
// based on device size.
static constexpr int kVisibleDestinationsCount = 5;

// A time delta from the Unix epoch to the beginning of the current day.
base::TimeDelta TodaysDay() {
  return base::Days(
      (base::Time::Now() - base::Time::UnixEpoch()).InDaysFloored());
}

OverflowMenuDestination* CreateOverflowMenuDestination(
    overflow_menu::Destination destination) {
  OverflowMenuDestination* result =
      [[OverflowMenuDestination alloc] initWithName:@"Foobar"
                                         symbolName:kSettingsSymbol
                                       systemSymbol:YES
                                   monochromeSymbol:NO
                            accessibilityIdentifier:@"Foobar"
                                 enterpriseDisabled:NO
                                displayNewLabelIcon:NO
                                            handler:^{
                                                // Do nothing
                                            }];

  result.destination = static_cast<NSInteger>(destination);

  return result;
}

OverflowMenuAction* CreateOverflowMenuAction(
    overflow_menu::ActionType actionType) {
  OverflowMenuAction* result =
      [[OverflowMenuAction alloc] initWithName:@"Foobar"
                                    symbolName:kSettingsSymbol
                                  systemSymbol:YES
                              monochromeSymbol:NO
                       accessibilityIdentifier:@"Foobar"
                            enterpriseDisabled:NO
                           displayNewLabelIcon:NO
                                       handler:^{
                                           // Do nothing
                                       }];

  result.actionType = static_cast<NSInteger>(actionType);

  return result;
}

}  // namespace

// Fake destination provider for test purposes.
@interface FakeOverflowMenuDestinationProvider
    : NSObject <OverflowMenuDestinationProvider>

@property(nonatomic, assign) DestinationRanking baseDestinations;

@property(nonatomic, assign) BOOL badgesCleared;

// By default, the provider will create a standard `OverflowMenuDestination`
// and return that in `-destinationForDestinationType:showAll:`. This will
// override that to return a custom destination.
- (void)storeCustomDestination:(OverflowMenuDestination*)destination
            forDestinationType:(overflow_menu::Destination)destinationType;

@end

@implementation FakeOverflowMenuDestinationProvider {
  std::map<overflow_menu::Destination, OverflowMenuDestination*>
      _destinationMap;
}

- (void)storeCustomDestination:(OverflowMenuDestination*)destination
            forDestinationType:(overflow_menu::Destination)destinationType {
  _destinationMap[destinationType] = destination;
}

- (OverflowMenuDestination*)destinationForDestinationType:
    (overflow_menu::Destination)destinationType {
  if (_destinationMap.contains(destinationType)) {
    return _destinationMap[destinationType];
  }
  return CreateOverflowMenuDestination(destinationType);
}

- (OverflowMenuDestination*)customizationDestinationForDestinationType:
    (overflow_menu::Destination)destinationType {
  return [self destinationForDestinationType:destinationType];
}

- (void)destinationCustomizationCompleted {
  self.badgesCleared = YES;
}

@end

// Fake action provider for test purposes.
@interface FakeOverflowMenuActionProvider
    : NSObject <OverflowMenuActionProvider>

@property(nonatomic, assign) ActionRanking basePageActions;

// By default, the provider will create a standard `OverflowMenuAction`
// and return that in `-actionForActionType:`. This will override
// that to return a custom action.
- (void)storeCustomAction:(OverflowMenuAction*)action
            forActionType:(overflow_menu::ActionType)actionType;

@end

@implementation FakeOverflowMenuActionProvider {
  std::map<overflow_menu::ActionType, OverflowMenuAction*> _actionMap;
}

- (void)storeCustomAction:(OverflowMenuAction*)action
            forActionType:(overflow_menu::ActionType)actionType {
  _actionMap[actionType] = action;
}

- (OverflowMenuAction*)actionForActionType:
    (overflow_menu::ActionType)actionType {
  if (_actionMap.contains(actionType)) {
    return _actionMap[actionType];
  }
  return CreateOverflowMenuAction(actionType);
}

- (OverflowMenuAction*)customizationActionForActionType:
    (overflow_menu::ActionType)actionType {
  return [self actionForActionType:actionType];
}

@end

class OverflowMenuOrdererTest : public PlatformTest {
 public:
  OverflowMenuOrdererTest() {}

 protected:
  void SetUp() override {
    destination_provider_ = [[FakeOverflowMenuDestinationProvider alloc] init];
    action_provider_ = [[FakeOverflowMenuActionProvider alloc] init];
  }

  void TearDown() override {
    [overflow_menu_orderer_ disconnect];

    PlatformTest::TearDown();
  }

  void InitializeOverflowMenuOrderer(BOOL isIncognito) {
    if (!prefs_) {
      CreatePrefs();
    }

    overflow_menu_model_ = [[OverflowMenuModel alloc] initWithDestinations:@[]
                                                              actionGroups:@[]];

    overflow_menu_orderer_ =
        [[OverflowMenuOrderer alloc] initWithIsIncognito:isIncognito];

    overflow_menu_orderer_.model = overflow_menu_model_;
    overflow_menu_orderer_.localStatePrefs = prefs_.get();
    overflow_menu_orderer_.visibleDestinationsCount = kVisibleDestinationsCount;
    overflow_menu_orderer_.destinationProvider = destination_provider_;
    overflow_menu_orderer_.actionProvider = action_provider_;
  }

  void InitializeOverflowMenuOrdererWithRanking(BOOL isIncognito,
                                                DestinationRanking ranking) {
    InitializeOverflowMenuOrderer(isIncognito);
    destination_provider_.baseDestinations = ranking;
    [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  }

  // Create pref registry for tests.
  void CreatePrefs() {
    prefs_ = std::make_unique<TestingPrefServiceSimple>();
    prefs_->registry()->RegisterDictionaryPref(
        prefs::kOverflowMenuDestinationUsageHistory, PrefRegistry::LOSSY_PREF);
    prefs_->registry()->RegisterListPref(prefs::kOverflowMenuNewDestinations,
                                         PrefRegistry::LOSSY_PREF);
    prefs_->registry()->RegisterListPref(prefs::kOverflowMenuDestinationsOrder);
    prefs_->registry()->RegisterListPref(
        prefs::kOverflowMenuHiddenDestinations);
    prefs_->registry()->RegisterDictionaryPref(
        prefs::kOverflowMenuDestinationBadgeData);
    prefs_->registry()->RegisterDictionaryPref(
        prefs::kOverflowMenuActionsOrder);
    prefs_->registry()->RegisterBooleanPref(
        prefs::kOverflowMenuDestinationUsageHistoryEnabled, true);
  }

  DestinationRanking SampleDestinations() {
    return {
        overflow_menu::Destination::Bookmarks,
        overflow_menu::Destination::History,
        overflow_menu::Destination::ReadingList,
        overflow_menu::Destination::Passwords,
        overflow_menu::Destination::Downloads,
        overflow_menu::Destination::RecentTabs,
        overflow_menu::Destination::SiteInfo,
        overflow_menu::Destination::Settings,
    };
  }

  ActionRanking SampleActions() {
    return {
        overflow_menu::ActionType::Follow,
        overflow_menu::ActionType::Bookmark,
        overflow_menu::ActionType::ReadingList,
        overflow_menu::ActionType::ClearBrowsingData,
        overflow_menu::ActionType::Translate,
        overflow_menu::ActionType::DesktopSite,
        overflow_menu::ActionType::FindInPage,
        overflow_menu::ActionType::TextZoom,
    };
  }

  DestinationRanking RankingFromDestinationArray(
      NSArray<OverflowMenuDestination*>* array) {
    DestinationRanking ranking;
    for (OverflowMenuDestination* destination : array) {
      ranking.push_back(
          static_cast<overflow_menu::Destination>(destination.destination));
    }
    return ranking;
  }

  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestingPrefServiceSimple> prefs_;
  OverflowMenuOrderer* overflow_menu_orderer_;
  OverflowMenuModel* overflow_menu_model_;
  FakeOverflowMenuDestinationProvider* destination_provider_;
  FakeOverflowMenuActionProvider* action_provider_;
};

// Tests that the destination ranking pref gets populated after sorting once.
TEST_F(OverflowMenuOrdererTest, StoresInitialDestinationRanking) {
  InitializeOverflowMenuOrderer(NO);
  DestinationRanking sample_destinations = SampleDestinations();
  destination_provider_.baseDestinations = sample_destinations;
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  const base::Value::List& stored_ranking =
      prefs_->GetList(prefs::kOverflowMenuDestinationsOrder);

  EXPECT_EQ(stored_ranking.size(), sample_destinations.size());
}

// Tests that the old pref format (kOverflowMenuDestinationUsageHistory as a
// dict containing both usage history and ranking) is correctly migrated to the
// new format (kOverflowMenuDestinationUsageHistory containing just usage
// history and kOverflowMenuDestinationsOrder containing ranking).
TEST_F(OverflowMenuOrdererTest, MigratesDestinationRanking) {
  CreatePrefs();

  base::Value::List old_ranking =
      base::Value::List()
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::Bookmarks))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::History));
  base::Value::Dict old_usage_history =
      base::Value::Dict()
          .Set(base::NumberToString(TodaysDay().InDays()),
               base::Value::Dict().Set(
                   overflow_menu::StringNameForDestination(
                       overflow_menu::Destination::Bookmarks),
                   5))
          .Set("ranking", old_ranking.Clone());

  prefs_->SetDict(prefs::kOverflowMenuDestinationUsageHistory,
                  std::move(old_usage_history));

  overflow_menu_orderer_ = [[OverflowMenuOrderer alloc] initWithIsIncognito:NO];

  // Set prefs here to force orderer to load and migrate.
  overflow_menu_orderer_.localStatePrefs = prefs_.get();

  const base::Value::List& new_ranking =
      prefs_->GetList(prefs::kOverflowMenuDestinationsOrder);
  const base::Value::Dict& new_usage_history =
      prefs_->GetDict(prefs::kOverflowMenuDestinationUsageHistory);

  EXPECT_EQ(new_ranking, old_ranking);
  EXPECT_EQ(1ul, new_usage_history.size());
}

TEST_F(OverflowMenuOrdererTest, InsertsNewDestinationInMiddleOfRanking) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
      all_destinations[6],
  };

  // Creates `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Same as `current_destinations`, but has a new element,
  // `all_destinations[7]`, which should eventually be inserted starting at
  // position 4 in the carousel (this is the expected behavior defined by
  // product).
  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      all_destinations[6],
      // New destination
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[3].destination),
            all_destinations[7]);
}

TEST_F(OverflowMenuOrdererTest, InsertsNewDestinationsInMiddleOfRanking) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Same as `current_destinations`, but has new elements (`all_destinations[6]`
  // and `all_destinations[7]`) inserted starting at position 4 in the carousel
  // (this is the expected behavior defined by product).
  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[7]);

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[6]);
}

TEST_F(OverflowMenuOrdererTest, InsertsAndRemovesNewDestinationsInRanking) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      // NOTE: all_destinations[0] was removed
      // NOTE: all_destinations[1] was removed
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[0].destination),
            all_destinations[2]);

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[7]);

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[6]);
}

// Tests that the destinations that have a badge are moved in the middle of the
// ranking to get the user's attention; before the untapped destinations.
TEST_F(OverflowMenuOrdererTest, MoveBadgedDestinationsInRanking) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[4]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[4]];

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[4]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[6]);
}

// Tests that the destinations that have an error badge have priority over the
// other badges when they are moved.
TEST_F(OverflowMenuOrdererTest, PriorityToErrorBadgeOverOtherBadges) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  OverflowMenuDestination* destination5 =
      CreateOverflowMenuDestination(all_destinations[5]);
  destination5.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination5
                             forDestinationType:all_destinations[5]];

  OverflowMenuDestination* destination3 =
      CreateOverflowMenuDestination(all_destinations[3]);
  destination3.badge = BadgeTypePromo;
  [destination_provider_ storeCustomDestination:destination3
                             forDestinationType:all_destinations[3]];

  destination_provider_.baseDestinations = current_destinations;

  // Initializes `OverflowMenuOrderer`.
  InitializeOverflowMenuOrderer(NO);

  // Set the initial ranking to `current_destinations`.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[5]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[3]);
}

// Tests that the destinations that have a badge but are in a better position
// than kNewDestinationsInsertionIndex won't be moved hence not demoted.
TEST_F(OverflowMenuOrdererTest, DontMoveBadgedDestinationWithGoodRanking) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[0]);
  destination.badge = BadgeTypePromo;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[0]];

  destination_provider_.baseDestinations = current_destinations;

  // Initializes `OverflowMenuOrderer`.
  InitializeOverflowMenuOrderer(NO);

  // Set the initial ranking to `current_destinations`.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // Verify that the destination with a badge and with a better ranking than
  // kNewDestinationsInsertionIndex wasn't moved.
  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[0].destination),
            all_destinations[0]);
}

// Tests that if a destination is both new and has a badge, it will be inserted
// before the other destinations that are only new without a badge assigned.
TEST_F(OverflowMenuOrdererTest, PriorityToBadgeOverNewDestinationStatus) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      // New destinations
      all_destinations[5],
      all_destinations[6],
      all_destinations[7],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[6]);
  destination.badge = BadgeTypeNew;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[6]];

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[6]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[7]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 2]
              .destination),
      all_destinations[5]);
}

// Tests that if a destination is both new and has a badge, it will be inserted
// before the other destinations wtih a badge of the same priority that are not
// new.
TEST_F(OverflowMenuOrdererTest, PriorityToNewDestinationWithBadge) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
      all_destinations[7],
  };

  OverflowMenuDestination* destination4 =
      CreateOverflowMenuDestination(all_destinations[4]);
  destination4.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination4
                             forDestinationType:all_destinations[4]];

  OverflowMenuDestination* destination5 =
      CreateOverflowMenuDestination(all_destinations[5]);
  destination5.badge = BadgeTypePromo;
  [destination_provider_ storeCustomDestination:destination5
                             forDestinationType:all_destinations[5]];

  OverflowMenuDestination* destination7 =
      CreateOverflowMenuDestination(all_destinations[7]);
  destination7.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination7
                             forDestinationType:all_destinations[7]];

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[7]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[4]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 2]
              .destination),
      all_destinations[5]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 3]
              .destination),
      all_destinations[6]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 4]
              .destination),
      all_destinations[3]);
}

// Tests that the destinations are still promoted when there is no usage
// history ranking.
TEST_F(OverflowMenuOrdererTest, TestNewDestinationsWhenNoHistoryUsageRanking) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
      all_destinations[6],
  };

  // Creates `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(YES, current_destinations);

  // Same as `current_destinations`, but has a new element,
  // `all_destinations[7]`, which should eventually be inserted starting at
  // position 4 in the carousel (this is the expected behavior defined by
  // product).
  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      all_destinations[6],
      // New destination
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[3].destination),
            all_destinations[7]);
}

TEST_F(OverflowMenuOrdererTest, MovesBadgedDestinationsWithNoUsageHistory) {
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[4]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[4]];

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  DestinationRanking updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);

  ASSERT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            all_destinations[4]);
  ASSERT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            all_destinations[6]);
}

// Tests that the action ranking pref gets populated after sorting once.
TEST_F(OverflowMenuOrdererTest, StoresInitialActionRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  InitializeOverflowMenuOrderer(NO);
  ActionRanking sample_actions = SampleActions();
  action_provider_.basePageActions = sample_actions;

  [overflow_menu_orderer_ updatePageActions];

  const base::Value::Dict& stored_ranking =
      prefs_->GetDict(prefs::kOverflowMenuActionsOrder);

  EXPECT_EQ(stored_ranking.size(), 2u);
  EXPECT_EQ(stored_ranking.FindList("shown")->size(), sample_actions.size());
}

// Tests that no destinations are lost if the overflow menu flag is enabled
// and then disabled
TEST_F(OverflowMenuOrdererTest,
       SavesHiddenDestinationsWhenOverflowCustomizationFlagDisabled) {
  base::test::ScopedFeatureList features;
  features.InitAndDisableFeature(kOverflowMenuCustomization);

  CreatePrefs();

  // Set the pref state to what it would be with flag enabled and some
  // destinations hidden.
  base::Value::List shown_destinations =
      base::Value::List()
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::ReadingList))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::Downloads));
  base::Value::List hidden_destinations =
      base::Value::List()
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::Bookmarks))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::History));

  base::Value::List all_destinations =
      base::Value::List()
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::ReadingList))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::Downloads))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::Bookmarks))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::History));

  prefs_->SetList(prefs::kOverflowMenuDestinationsOrder,
                  std::move(shown_destinations));
  prefs_->SetList(prefs::kOverflowMenuHiddenDestinations,
                  std::move(hidden_destinations));

  overflow_menu_orderer_ = [[OverflowMenuOrderer alloc] initWithIsIncognito:NO];

  // Set prefs here to force orderer to load and migrate.
  overflow_menu_orderer_.localStatePrefs = prefs_.get();

  const base::Value::List& new_order =
      prefs_->GetList(prefs::kOverflowMenuDestinationsOrder);

  EXPECT_EQ(new_order, all_destinations);
}

// Tests that reenabling destnation usage history clears the destination usage
// history and moves all destinations back to shown.
TEST_F(OverflowMenuOrdererTest, EnablingDestinationUsageHistory) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  CreatePrefs();

  // Set the pref state to what it would be with destination usage history
  // disabled and some destinations hidden.
  base::Value::List shown_destinations =
      base::Value::List()
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::ReadingList))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::Downloads));
  base::Value::List hidden_destinations =
      base::Value::List()
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::Bookmarks))
          .Append(overflow_menu::StringNameForDestination(
              overflow_menu::Destination::History));
  prefs_->SetList(prefs::kOverflowMenuDestinationsOrder,
                  std::move(shown_destinations));
  prefs_->SetList(prefs::kOverflowMenuHiddenDestinations,
                  std::move(hidden_destinations));

  prefs_->SetBoolean(prefs::kOverflowMenuDestinationUsageHistoryEnabled, false);

  // Destination Usage History prefs
  base::TimeDelta day = TodaysDay() - base::Days(1);
  std::string bookmarkName = overflow_menu::StringNameForDestination(
      overflow_menu::Destination::Bookmarks);
  base::Value::Dict day_history =
      base::Value::Dict().Set(base::NumberToString(day.InDays()),
                              base::Value::Dict().Set(bookmarkName, 1));
  prefs_->SetDict(prefs::kOverflowMenuDestinationUsageHistory,
                  std::move(day_history));

  overflow_menu_orderer_ = [[OverflowMenuOrderer alloc] initWithIsIncognito:NO];

  overflow_menu_model_ = [[OverflowMenuModel alloc] initWithDestinations:@[]
                                                            actionGroups:@[]];
  overflow_menu_orderer_.model = overflow_menu_model_;
  overflow_menu_orderer_.localStatePrefs = prefs_.get();
  overflow_menu_orderer_.visibleDestinationsCount = kVisibleDestinationsCount;
  overflow_menu_orderer_.destinationProvider = destination_provider_;
  overflow_menu_orderer_.actionProvider = action_provider_;

  // Reenable Destination Usage History.
  DestinationCustomizationModel* customizationModel =
      overflow_menu_orderer_.destinationCustomizationModel;
  customizationModel.destinationUsageEnabled = YES;
  [overflow_menu_orderer_ commitDestinationsUpdate];

  ASSERT_EQ([overflow_menu_model_.destinations count], 4u);
  EXPECT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[0].destination),
            overflow_menu::Destination::ReadingList);
  EXPECT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[1].destination),
            overflow_menu::Destination::Downloads);
  EXPECT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[2].destination),
            overflow_menu::Destination::Bookmarks);
  EXPECT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[3].destination),
            overflow_menu::Destination::History);

  const base::Value::Dict& new_history =
      prefs_->GetDict(prefs::kOverflowMenuDestinationUsageHistory);
  EXPECT_EQ(new_history.size(), 0u);
}

// Tests that new actions in code are added to the ranking
TEST_F(OverflowMenuOrdererTest, AddsNewActionsToRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  CreatePrefs();

  // Set the pref state what it would be with a few actions shown and hidden.
  base::Value::Dict actions_order =
      base::Value::Dict()
          .Set("shown", base::Value::List()
                            .Append(overflow_menu::StringNameForActionType(
                                overflow_menu::ActionType::Follow))
                            .Append(overflow_menu::StringNameForActionType(
                                overflow_menu::ActionType::Bookmark)))
          .Set("hidden",
               base::Value::List()
                   .Append(overflow_menu::StringNameForActionType(
                       overflow_menu::ActionType::ReadingList))
                   .Append(overflow_menu::StringNameForActionType(
                       overflow_menu::ActionType::ClearBrowsingData)));

  prefs_->SetDict(prefs::kOverflowMenuActionsOrder, std::move(actions_order));

  InitializeOverflowMenuOrderer(NO);
  ActionRanking sample_actions = SampleActions();
  action_provider_.basePageActions = sample_actions;

  OverflowMenuActionGroup* group =
      [[OverflowMenuActionGroup alloc] initWithGroupName:@"test"
                                                 actions:@[]
                                                  footer:nil];
  overflow_menu_orderer_.pageActionsGroup = group;

  [overflow_menu_orderer_ updatePageActions];

  ASSERT_EQ(group.actions.count, 6u);
  // The action order should first be shown actions, and then any new actions in
  // order.
  EXPECT_EQ(static_cast<overflow_menu::ActionType>(group.actions[0].actionType),
            overflow_menu::ActionType::Follow);
  EXPECT_EQ(static_cast<overflow_menu::ActionType>(group.actions[1].actionType),
            overflow_menu::ActionType::Bookmark);
  EXPECT_EQ(static_cast<overflow_menu::ActionType>(group.actions[2].actionType),
            overflow_menu::ActionType::Translate);
  EXPECT_EQ(static_cast<overflow_menu::ActionType>(group.actions[3].actionType),
            overflow_menu::ActionType::DesktopSite);
  EXPECT_EQ(static_cast<overflow_menu::ActionType>(group.actions[4].actionType),
            overflow_menu::ActionType::FindInPage);
  EXPECT_EQ(static_cast<overflow_menu::ActionType>(group.actions[5].actionType),
            overflow_menu::ActionType::TextZoom);
}

// Tests that when there is a badged item, the overflow menu orderer doesn't
// change the order via the destination usage history.
TEST_F(OverflowMenuOrdererTest, NoDestinationUsageHistoryWithBadge) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();

  // Destination 6 will be badged and destination 5 will be tapped many times,
  // so it typically would be reordered.
  overflow_menu::Destination badged_destination = all_destinations[6];
  overflow_menu::Destination tapped_destination = all_destinations[5];
  DestinationRanking initial_ranking = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], tapped_destination,
      badged_destination,
  };

  InitializeOverflowMenuOrdererWithRanking(NO, initial_ranking);
  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(badged_destination);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:badged_destination];

  // Tap the last destination many times, so it would typically be reordered.
  for (int i = 0; i < 5; i++) {
    [overflow_menu_orderer_ recordClickForDestination:tapped_destination];
  }

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  DestinationRanking updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  // The expected result is that the badged destination is moved, but the tapped
  // destination is not because there is an active badge.
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // After 2 more impressions, the badge will no longer affect ordering.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // Now, on the next reordering, destination usage history should take effect.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  [overflow_menu_orderer_ updateForMenuDisappearance];
  EXPECT_EQ(updated_ranking[0], tapped_destination);
}

// Tests that a newly added menu item only has a new badge for a short time.
TEST_F(OverflowMenuOrdererTest, NewItemOnlyHasBadgeForShortTime) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking initial_ranking = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4],
  };

  InitializeOverflowMenuOrdererWithRanking(NO, initial_ranking);

  overflow_menu::Destination new_destination = all_destinations[5];
  DestinationRanking ranking_with_new = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], new_destination,
  };
  destination_provider_.baseDestinations = ranking_with_new;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // The new item should be moved up.
  EXPECT_EQ(
      RankingFromDestinationArray(
          overflow_menu_model_.destinations)[kNewDestinationsInsertionIndex],
      new_destination);
  EXPECT_EQ(
      overflow_menu_model_.destinations[kNewDestinationsInsertionIndex].badge,
      BadgeTypeNew);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // For the next 2 impressions, the destination should still have a badge
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  EXPECT_EQ(
      overflow_menu_model_.destinations[kNewDestinationsInsertionIndex].badge,
      BadgeTypeNew);
  [overflow_menu_orderer_ updateForMenuDisappearance];
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  EXPECT_EQ(
      overflow_menu_model_.destinations[kNewDestinationsInsertionIndex].badge,
      BadgeTypeNew);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // Now, on the next reordering, destination should no longer have a badge.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  EXPECT_EQ(
      overflow_menu_model_.destinations[kNewDestinationsInsertionIndex].badge,
      BadgeTypeNone);
  [overflow_menu_orderer_ updateForMenuDisappearance];
}

// Tests that if two items are badged, with one being below the threshold, its
// impression counter doesn't count down until the first item's counter hits 0.
TEST_F(OverflowMenuOrdererTest, TwoBadgesOnlyOneCountsImpressions) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();

  // Destinations 6 and 7 will be badged and destination 5 will be tapped many
  // times, so it typically would be reordered.
  overflow_menu::Destination badged_destination1 = all_destinations[6];
  overflow_menu::Destination badged_destination2 = all_destinations[7];
  overflow_menu::Destination tapped_destination = all_destinations[5];
  DestinationRanking initial_ranking = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], tapped_destination,
      badged_destination1, badged_destination2,
  };

  InitializeOverflowMenuOrdererWithRanking(NO, initial_ranking);
  OverflowMenuDestination* destination1 =
      CreateOverflowMenuDestination(badged_destination1);
  destination1.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination1
                             forDestinationType:badged_destination1];
  OverflowMenuDestination* destination2 =
      CreateOverflowMenuDestination(badged_destination2);
  destination2.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination2
                             forDestinationType:badged_destination2];

  // Tap the last destination many times, so it would typically be reordered.
  for (int i = 0; i < 5; i++) {
    [overflow_menu_orderer_ recordClickForDestination:tapped_destination];
  }

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  DestinationRanking updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  // The expected result is that the badged destinations are moved, but the
  // tapped destination is not because there is an active badge.
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination2);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            badged_destination1);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // After 2 more impressions, the first badge will no longer affect ordering.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination2);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            badged_destination1);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination2);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            badged_destination1);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // Now, on the next reordering, the second badged item should be in prime
  // position.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination1);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            badged_destination2);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination1);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            badged_destination2);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            badged_destination1);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            badged_destination2);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // Finally, now that 6 impressions have happened, the tapped item will move.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[0], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];
}

// Tests that if two items are badged at the front of the list, their impression
// counters count down simultaneously.
TEST_F(OverflowMenuOrdererTest, TwoBadgesAtBeginningCountTogether) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();

  // Destinations 0 and 1 will be badged and destination 7 will be tapped many
  // times, so it typically would be reordered.
  overflow_menu::Destination badged_destination1 = all_destinations[0];
  overflow_menu::Destination badged_destination2 = all_destinations[1];
  overflow_menu::Destination tapped_destination = all_destinations[7];
  DestinationRanking initial_ranking = {
      badged_destination1, badged_destination2, all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
      all_destinations[6], tapped_destination,
  };

  InitializeOverflowMenuOrdererWithRanking(NO, initial_ranking);
  OverflowMenuDestination* destination1 =
      CreateOverflowMenuDestination(badged_destination1);
  destination1.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination1
                             forDestinationType:badged_destination1];
  OverflowMenuDestination* destination2 =
      CreateOverflowMenuDestination(badged_destination2);
  destination2.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination2
                             forDestinationType:badged_destination2];

  // Tap the last destination many times, so it would typically be reordered.
  for (int i = 0; i < 5; i++) {
    [overflow_menu_orderer_ recordClickForDestination:tapped_destination];
  }

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  DestinationRanking updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  // The expected result is that the badged destinations have not moved because
  // they are already visible, and the tapped destination has not because there
  // is an active badge.
  EXPECT_EQ(updated_ranking[0], badged_destination1);
  EXPECT_EQ(updated_ranking[1], badged_destination2);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // After 2 more impressions, the badges will no longer affect ordering.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[0], badged_destination1);
  EXPECT_EQ(updated_ranking[1], badged_destination2);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[0], badged_destination1);
  EXPECT_EQ(updated_ranking[1], badged_destination2);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  // Finally, now that all badges have run out of impressions, the tapped item
  // will move.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[0], tapped_destination);
  [overflow_menu_orderer_ updateForMenuDisappearance];
}

// Tests that if an item has a new badge and the destinations are customized,
// those badges are cleared
TEST_F(OverflowMenuOrdererTest, CustomizingDestinationsClearsBadgeImpressions) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();

  // Destination 0 will be badged and destination 6 will be tapped many times,
  // so it typically would be reordered. Destination 7 is new.
  overflow_menu::Destination badged_destination = all_destinations[0];
  overflow_menu::Destination tapped_destination = all_destinations[6];
  DestinationRanking initial_ranking = {
      badged_destination,  all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
      tapped_destination,
  };

  InitializeOverflowMenuOrdererWithRanking(NO, initial_ranking);
  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(badged_destination);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:badged_destination];

  // Tap the last destination many times, so it would typically be reordered.
  for (int i = 0; i < 5; i++) {
    [overflow_menu_orderer_ recordClickForDestination:tapped_destination];
  }

  overflow_menu::Destination new_destination = all_destinations[7];
  DestinationRanking ranking_with_new = {initial_ranking[0], initial_ranking[1],
                                         initial_ranking[2], initial_ranking[3],
                                         initial_ranking[4], initial_ranking[5],
                                         initial_ranking[6], new_destination};
  destination_provider_.baseDestinations = ranking_with_new;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  DestinationRanking updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  // The expected result is that the badged error destination has not moved
  // because it is already visible, the new destination has moved and has a
  // badge, and the tapped destination is not because there is an active badge.
  EXPECT_EQ(updated_ranking[0], badged_destination);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex], new_destination);
  EXPECT_EQ(updated_ranking[updated_ranking.size() - 1], tapped_destination);
  EXPECT_FALSE(destination_provider_.badgesCleared);

  // Edit the menu, which should clear the badge impressions remaining.
  DestinationCustomizationModel* customizationModel =
      overflow_menu_orderer_.destinationCustomizationModel;
  OverflowMenuDestination* destinationToHide =
      customizationModel.shownDestinations[6];
  ASSERT_EQ(destinationToHide.destination,
            static_cast<int>(initial_ranking[5]));
  destinationToHide.shown = NO;
  customizationModel.destinationUsageEnabled = YES;
  [overflow_menu_orderer_ commitDestinationsUpdate];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  // With badges cleared, the tapped item should move again
  EXPECT_EQ(updated_ranking[0], tapped_destination);
  EXPECT_TRUE(destination_provider_.badgesCleared);
}

// Variant of `InsertsNewDestinationInMiddleOfRanking` with customization flag
// enabled.
TEST_F(OverflowMenuOrdererTest,
       Customization_InsertsNewDestinationInMiddleOfRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
      all_destinations[6],
  };

  // Creates `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Same as `current_destinations`, but has a new element,
  // `all_destinations[7]`, which should eventually be inserted starting at
  // position 4 in the carousel (this is the expected behavior defined by
  // product).
  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      all_destinations[6],
      // New destination
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[3].destination),
            all_destinations[7]);
}

// Variant of `InsertsNewDestinationsInMiddleOfRanking` with customization flag
// enabled.
TEST_F(OverflowMenuOrdererTest,
       Customization_InsertsNewDestinationsInMiddleOfRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Same as `current_destinations`, but has new elements (`all_destinations[6]`
  // and `all_destinations[7]`) inserted starting at position 4 in the carousel
  // (this is the expected behavior defined by product).
  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[7]);

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[6]);
}

// Variant of `InsertsAndRemovesNewDestinationsInRanking` with customization
// flag enabled.
TEST_F(OverflowMenuOrdererTest,
       Customization_InsertsAndRemovesNewDestinationsInRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      // NOTE: all_destinations[0] was removed
      // NOTE: all_destinations[1] was removed
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[0].destination),
            all_destinations[2]);

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[7]);

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[6]);
}

// Variant of `MoveBadgedDestinationsInRanking` with customization flag enabled.
// Tests that the destinations that have a badge are moved in the middle of the
// ranking to get the user's attention; before the untapped destinations.
TEST_F(OverflowMenuOrdererTest, Customization_MoveBadgedDestinationsInRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[4]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[4]];

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[4]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[6]);
}

// Variant of `PriorityToErrorBadgeOverOtherBadges` with customization flag
// enabled. Tests that the destinations that have an error badge have priority
// over the other badges when they are moved.
TEST_F(OverflowMenuOrdererTest,
       Customization_PriorityToErrorBadgeOverOtherBadges) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  OverflowMenuDestination* destination5 =
      CreateOverflowMenuDestination(all_destinations[5]);
  destination5.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination5
                             forDestinationType:all_destinations[5]];

  OverflowMenuDestination* destination3 =
      CreateOverflowMenuDestination(all_destinations[3]);
  destination3.badge = BadgeTypePromo;
  [destination_provider_ storeCustomDestination:destination3
                             forDestinationType:all_destinations[3]];

  destination_provider_.baseDestinations = current_destinations;

  // Initializes `OverflowMenuOrderer`.
  InitializeOverflowMenuOrderer(NO);

  // Set the initial ranking to `current_destinations`.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex]
              .destination),
      all_destinations[5]);
  ASSERT_EQ(
      static_cast<overflow_menu::Destination>(
          overflow_menu_model_.destinations[kNewDestinationsInsertionIndex + 1]
              .destination),
      all_destinations[3]);
}

// Variant of `DontMoveBadgedDestinationWithGoodRanking` with customization flag
// enabled. Tests that the destinations that have a badge but are in a better
// position than kNewDestinationsInsertionIndex won't be moved hence not
// demoted.
TEST_F(OverflowMenuOrdererTest,
       Customization_DontMoveBadgedDestinationWithGoodRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[0]);
  destination.badge = BadgeTypePromo;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[0]];

  destination_provider_.baseDestinations = current_destinations;

  // Initializes `OverflowMenuOrderer`.
  InitializeOverflowMenuOrderer(NO);

  // Set the initial ranking to `current_destinations`.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // Verify that the destination with a badge and with a better ranking than
  // kNewDestinationsInsertionIndex wasn't moved.
  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[0].destination),
            all_destinations[0]);
}

// Variant of `TestNewDestinationsWhenNoHistoryUsageRanking` with customization
// flag enabled. Tests that the destinations are still promoted when there is no
// usage history ranking.
TEST_F(OverflowMenuOrdererTest,
       Customization_TestNewDestinationsWhenNoHistoryUsageRanking) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
      all_destinations[6],
  };

  // Creates `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(YES, current_destinations);

  // Same as `current_destinations`, but has a new element,
  // `all_destinations[7]`, which should eventually be inserted starting at
  // position 4 in the carousel (this is the expected behavior defined by
  // product).
  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      all_destinations[6],
      // New destination
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[3].destination),
            all_destinations[7]);
}

// Variant of `MovesBadgedDestinationsWithNoUsageHistory` with customization
// flag enabled.
TEST_F(OverflowMenuOrdererTest,
       Customization_MovesBadgedDestinationsWithNoUsageHistory) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[4]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[4]];

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  DestinationRanking updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);

  ASSERT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            all_destinations[4]);
  ASSERT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            all_destinations[6]);
}

// Tests that calling the simple `-updateDestinations` method doesn't reorder
// the menu.
TEST_F(OverflowMenuOrdererTest, UpdateDoesntReorderMenu) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);
  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[5]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[5]];

  [overflow_menu_orderer_ updateDestinations];

  // The badged destination should not have changed position.
  ASSERT_EQ(static_cast<overflow_menu::Destination>(
                overflow_menu_model_.destinations[5].destination),
            all_destinations[5]);
}

// Tests that if a hidden destination gets an error badge, that badge appears on
// settings instead.
TEST_F(OverflowMenuOrdererTest, HiddenDestinationPropagatesErrorBadge) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      overflow_menu::Destination::Settings,
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
  };

  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Hide destination 5.
  DestinationCustomizationModel* destinationModel =
      overflow_menu_orderer_.destinationCustomizationModel;
  destinationModel.shownDestinations[5].shown = NO;
  [overflow_menu_orderer_ commitDestinationsUpdate];

  // Add error badge to destination 5
  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[5]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[5]];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // The badge on the hidden destination 5 should propagate to the settings
  // destination.
  ASSERT_EQ(overflow_menu_model_.destinations[0].badge, BadgeTypeError);

  // Removing the badge on hidden destination 5 should also propagate to the
  // settings destination
  destination.badge = BadgeTypeNone;
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  ASSERT_EQ(overflow_menu_model_.destinations[0].badge, BadgeTypeNone);
}

// Tests that the overflow menu order loads badge data from prefs.
TEST_F(OverflowMenuOrdererTest, LoadBadgeDataFromPrefs) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  CreatePrefs();

  base::Value::Dict badge_data =
      base::Value::Dict().Set(overflow_menu::StringNameForDestination(
                                  overflow_menu::Destination::Bookmarks),
                              base::Value::Dict()
                                  .Set("impressions_remaining", 2)
                                  .Set("badge_type", "error")
                                  .Set("is_feature_driven_badge", false));

  prefs_->SetDict(prefs::kOverflowMenuDestinationBadgeData,
                  std::move(badge_data));

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4],
      all_destinations[5], overflow_menu::Destination::Bookmarks,
  };

  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            overflow_menu::Destination::Bookmarks);

  // Adding another new destination should cause Bookmarks to hold the 4th slot
  // until its impression timer is up
  DestinationRanking new_destinations = {
      all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4],
      all_destinations[5], overflow_menu::Destination::Bookmarks,
      all_destinations[6],
  };
  destination_provider_.baseDestinations = new_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            overflow_menu::Destination::Bookmarks);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            all_destinations[6]);
  [overflow_menu_orderer_ updateForMenuDisappearance];

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            overflow_menu::Destination::Bookmarks);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            all_destinations[6]);
  [overflow_menu_orderer_ updateForMenuDisappearance];
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // After the 2 impressions, the new item should be promoted, while the
  // originally-badged item should fall back.
  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];
  updated_ranking =
      RankingFromDestinationArray(overflow_menu_model_.destinations);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex],
            all_destinations[6]);
  EXPECT_EQ(updated_ranking[kNewDestinationsInsertionIndex + 1],
            overflow_menu::Destination::Bookmarks);
  [overflow_menu_orderer_ updateForMenuDisappearance];
}

// Tests that the overflow menu order saves badge data to prefs.
TEST_F(OverflowMenuOrdererTest, SaveBadgeDataToPrefs) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
      all_destinations[6],
  };

  // Creates `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Same as `current_destinations`, but has a new element,
  // `all_destinations[7]`.
  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      all_destinations[6],
      // New destination
      all_destinations[7],
  };

  destination_provider_.baseDestinations = updated_destinations;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  const base::Value::Dict& badge_data =
      prefs_->GetDict(prefs::kOverflowMenuDestinationBadgeData);

  std::string new_destination_string =
      overflow_menu::StringNameForDestination(all_destinations[7]);

  ASSERT_TRUE(badge_data.contains(new_destination_string));

  EXPECT_EQ(3, badge_data.FindDict(new_destination_string)
                   ->FindInt("impressions_remaining"));
  EXPECT_EQ(
      "new",
      *badge_data.FindDict(new_destination_string)->FindString("badge_type"));
  EXPECT_EQ(false, badge_data.FindDict(new_destination_string)
                       ->FindBool("is_feature_driven_badge"));
}

// Tests that correct metrics are fired when the menu is reordered due to a new
// badge.
TEST_F(OverflowMenuOrdererTest, Customization_NewBadgeReorderingMetrics) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
  };

  destination_provider_.baseDestinations = updated_destinations;

  base::HistogramTester tester;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // There was one new destination and one with an error badge.
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.DestinationsOrderChangedProgrammatically", 1, 1);
}

// Tests that correct metrics are fired when the menu is reordered due to an
// error badge.
TEST_F(OverflowMenuOrdererTest, Customization_ErrorBadgeReorderingMetrics) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[4]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[4]];

  base::HistogramTester tester;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // There was one destination with an error badge.
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.DestinationsOrderChangedProgrammatically", 0, 1);
}

// Tests that correct metrics are fired when the menu is reordered due to both a
// new and error badge.
TEST_F(OverflowMenuOrdererTest,
       Customization_NewAndErrorBadgeReorderingMetrics) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  DestinationRanking updated_destinations = {
      all_destinations[0],
      all_destinations[1],
      all_destinations[2],
      all_destinations[3],
      all_destinations[4],
      all_destinations[5],
      // New destinations
      all_destinations[6],
  };

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[4]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[4]];

  destination_provider_.baseDestinations = updated_destinations;

  base::HistogramTester tester;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // There was one new destination and one with an error badge.
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.DestinationsOrderChangedProgrammatically", 2, 1);
}

// Tests that no metrics are fired when there are badges, but they do not cause
// the menu to be re-ordered.
TEST_F(OverflowMenuOrdererTest, Customization_BadgeReorderingMetricsNotFired) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  // Initializes `OverflowMenuOrderer` with initial ranking
  // `current_destinations`.
  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  OverflowMenuDestination* destination =
      CreateOverflowMenuDestination(all_destinations[0]);
  destination.badge = BadgeTypeError;
  [destination_provider_ storeCustomDestination:destination
                             forDestinationType:all_destinations[0]];

  base::HistogramTester tester;

  [overflow_menu_orderer_ reorderDestinationsForInitialMenu];

  // The badge did not cause the menu to change order.
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.DestinationsOrderChangedProgrammatically", 0, 0);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.DestinationsOrderChangedProgrammatically", 1, 0);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.DestinationsOrderChangedProgrammatically", 2, 0);
}

// Tests that the proper metrics are recorded when destination customization
// completes.
TEST_F(OverflowMenuOrdererTest, DestinationCustomizationRecordsMetrics) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Hide destinations 4 and 5.
  DestinationCustomizationModel* destinationModel =
      overflow_menu_orderer_.destinationCustomizationModel;

  OverflowMenuDestination* destination4 = destinationModel.shownDestinations[4];
  OverflowMenuDestination* destination5 = destinationModel.shownDestinations[5];
  destination4.shown = NO;
  destination5.shown = NO;

  base::HistogramTester tester;

  [overflow_menu_orderer_ commitDestinationsUpdate];

  tester.ExpectBucketCount("IOS.OverflowMenu.SmartSortingStateChange", 1, 1);

  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.DestinationRemoved",
                          2);
  tester.ExpectBucketCount("IOS.OverflowMenu.Customization.DestinationRemoved",
                           4, 1);
  tester.ExpectBucketCount("IOS.OverflowMenu.Customization.DestinationRemoved",
                           5, 1);

  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.FirstPosition", 0,
      1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.SecondPosition", 1,
      1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.ThirdPosition", 2,
      1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.FourthPosition", 3,
      1);

  std::vector<base::Bucket> buckets = tester.GetAllSamples(
      "IOS.OverflowMenu.Customization.DestinationsCustomized");
  ASSERT_EQ(1U, buckets.size());
  EXPECT_TRUE(buckets[0].min & 1 << 0);
  EXPECT_FALSE(buckets[0].min & 1 << 1);
  EXPECT_FALSE(buckets[0].min & 1 << 2);
  EXPECT_TRUE(buckets[0].min & 1 << 3);
  EXPECT_FALSE(buckets[0].min & 1 << 4);
  EXPECT_FALSE(buckets[0].min & 1 << 5);

  // Now turn destination 4 (the first hidden destination) back on and also turn
  // destination usage history back on.
  destinationModel = overflow_menu_orderer_.destinationCustomizationModel;
  destinationModel.hiddenDestinations[0].shown = YES;
  destinationModel.destinationUsageEnabled = YES;

  [overflow_menu_orderer_ commitDestinationsUpdate];

  tester.ExpectBucketCount("IOS.OverflowMenu.SmartSortingStateChange", 0, 1);

  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.DestinationAdded", 1);
  tester.ExpectBucketCount("IOS.OverflowMenu.Customization.DestinationAdded", 4,
                           1);

  // Destinations are added to the front of the list, so the new one now
  // occupies the first position.
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.FirstPosition", 4,
      1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.SecondPosition", 0,
      1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.ThirdPosition", 1,
      1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.FourthPosition", 2,
      1);

  buckets = tester.GetAllSamples(
      "IOS.OverflowMenu.Customization.DestinationsCustomized");
  ASSERT_EQ(2U, buckets.size());
  EXPECT_FALSE(buckets[1].min & 1 << 0);
  EXPECT_TRUE(buckets[1].min & 1 << 1);
  EXPECT_TRUE(buckets[1].min & 1 << 2);
  EXPECT_FALSE(buckets[1].min & 1 << 3);
  EXPECT_TRUE(buckets[1].min & 1 << 4);
  EXPECT_FALSE(buckets[1].min & 1 << 5);
}

// Tests that no metrics are recorded if there were no actual changes.
TEST_F(OverflowMenuOrdererTest, DestinationCustomizationNoMetricsIfNoChange) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  DestinationRanking all_destinations = SampleDestinations();
  DestinationRanking current_destinations = {
      all_destinations[0], all_destinations[1], all_destinations[2],
      all_destinations[3], all_destinations[4], all_destinations[5],
  };

  InitializeOverflowMenuOrdererWithRanking(NO, current_destinations);

  // Access model to start customization session
  DestinationCustomizationModel* destinationModel =
      overflow_menu_orderer_.destinationCustomizationModel;

  ASSERT_EQ(destinationModel.hasChanged, NO);

  base::HistogramTester tester;

  [overflow_menu_orderer_ commitDestinationsUpdate];

  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.DestinationRemoved",
                          0);
  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.DestinationAdded", 0);
  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.DestinationsCustomized", 0);

  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.FirstPosition", 0);
  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.SecondPosition", 0);
  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.ThirdPosition", 0);
  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.DestinationsReordered.FourthPosition", 0);
}

// Tests that the proper metrics are recorded when action customization
// completes.
TEST_F(OverflowMenuOrdererTest, ActionCustomizationRecordsMetrics) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  ActionRanking all_actions = SampleActions();
  ActionRanking current_actions = {
      all_actions[0], all_actions[1], all_actions[2],
      all_actions[3], all_actions[4], all_actions[5],
  };

  InitializeOverflowMenuOrderer(NO);
  action_provider_.basePageActions = current_actions;
  [overflow_menu_orderer_ updatePageActions];

  // Hide actions 4 and 5.
  ActionCustomizationModel* actionModel =
      overflow_menu_orderer_.actionCustomizationModel;

  OverflowMenuAction* action4 = actionModel.shownActions[4];
  OverflowMenuAction* action5 = actionModel.shownActions[5];
  action4.shown = NO;
  action5.shown = NO;

  base::HistogramTester tester;

  [overflow_menu_orderer_ commitActionsUpdate];

  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.ActionRemoved", 2);
  tester.ExpectBucketCount("IOS.OverflowMenu.Customization.ActionRemoved", 8,
                           1);
  tester.ExpectBucketCount("IOS.OverflowMenu.Customization.ActionRemoved", 9,
                           1);

  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.FirstPosition", 4, 1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.SecondPosition", 5, 1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.ThirdPosition", 6, 1);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.FourthPosition", 7, 1);

  std::vector<base::Bucket> buckets =
      tester.GetAllSamples("IOS.OverflowMenu.Customization.ActionsCustomized");
  ASSERT_EQ(1U, buckets.size());
  EXPECT_TRUE(buckets[0].min & 1 << 0);
  EXPECT_FALSE(buckets[0].min & 1 << 1);
  EXPECT_FALSE(buckets[0].min & 1 << 2);

  // Now turn action 4 (the first hidden action) back on.
  actionModel = overflow_menu_orderer_.actionCustomizationModel;
  actionModel.hiddenActions[0].shown = YES;

  [overflow_menu_orderer_ commitActionsUpdate];

  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.ActionAdded", 1);
  tester.ExpectBucketCount("IOS.OverflowMenu.Customization.ActionAdded", 8, 1);

  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.FirstPosition", 4, 2);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.SecondPosition", 5, 2);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.ThirdPosition", 6, 2);
  tester.ExpectBucketCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.FourthPosition", 7, 2);

  buckets =
      tester.GetAllSamples("IOS.OverflowMenu.Customization.ActionsCustomized");
  ASSERT_EQ(2U, buckets.size());
  EXPECT_FALSE(buckets[1].min & 1 << 0);
  EXPECT_TRUE(buckets[1].min & 1 << 1);
  EXPECT_FALSE(buckets[1].min & 1 << 2);
}

// Tests that no metrics are recorded if there were no actual changes.
TEST_F(OverflowMenuOrdererTest, ActionCustomizationNoMetricsIfNoChange) {
  base::test::ScopedFeatureList features(kOverflowMenuCustomization);

  ActionRanking all_actions = SampleActions();
  ActionRanking current_actions = {
      all_actions[0], all_actions[1], all_actions[2],
      all_actions[3], all_actions[4], all_actions[5],
  };

  InitializeOverflowMenuOrderer(NO);
  action_provider_.basePageActions = current_actions;
  [overflow_menu_orderer_ updatePageActions];

  // Access model to start customization session
  ActionCustomizationModel* actionModel =
      overflow_menu_orderer_.actionCustomizationModel;

  ASSERT_EQ(actionModel.hasChanged, NO);

  base::HistogramTester tester;

  [overflow_menu_orderer_ commitActionsUpdate];

  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.ActionRemoved", 0);
  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.ActionAdded", 0);
  tester.ExpectTotalCount("IOS.OverflowMenu.Customization.ActionsCustomized",
                          0);

  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.FirstPosition", 0);
  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.SecondPosition", 0);
  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.ThirdPosition", 0);
  tester.ExpectTotalCount(
      "IOS.OverflowMenu.Customization.ActionsReordered.FourthPosition", 0);
}