chromium/ios/chrome/browser/tabs/model/inactive_tabs/utils_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/tabs/model/inactive_tabs/utils.h"

#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "base/test/task_environment.h"
#import "components/tab_groups/tab_group_id.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper_delegate.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/web_state_list/test/fake_web_state_list_delegate.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/snapshots/model/snapshot_browser_agent.h"
#import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
#import "ios/chrome/browser/tabs/model/inactive_tabs/features.h"
#import "ios/chrome/browser/tabs/model/inactive_tabs/utils.h"
#import "ios/chrome/browser/web/model/web_navigation_util.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "ui/base/device_form_factor.h"

using tab_groups::TabGroupId;

// Fake WebStateList delegate that attaches the required tab helper.
class InactiveTabsFakeWebStateListDelegate : public FakeWebStateListDelegate {
 public:
  InactiveTabsFakeWebStateListDelegate() {}
  ~InactiveTabsFakeWebStateListDelegate() override {}

  // WebStateListDelegate implementation.
  void WillAddWebState(web::WebState* web_state) override {
    SnapshotTabHelper::CreateForWebState(web_state);
  }
};

class InactiveTabsUtilsTest : public PlatformTest {
 public:
  InactiveTabsUtilsTest() {
    browser_state_ = TestChromeBrowserState::Builder().Build();
    browser_active_ = std::make_unique<TestBrowser>(
        browser_state_.get(),
        std::make_unique<InactiveTabsFakeWebStateListDelegate>());
    browser_inactive_ = std::make_unique<TestBrowser>(
        browser_state_.get(),
        std::make_unique<InactiveTabsFakeWebStateListDelegate>());
    SnapshotBrowserAgent::CreateForBrowser(browser_active_.get());
    SnapshotBrowserAgent::CreateForBrowser(browser_inactive_.get());
  }

  PrefService* local_state() {
    return GetApplicationContext()->GetLocalState();
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<TestBrowser> browser_active_;
  std::unique_ptr<TestBrowser> browser_inactive_;
  // Used to verify histogram logging.
  base::HistogramTester histogram_tester_;

  std::unique_ptr<web::FakeWebState> CreateTab(
      web::WebStateID unique_identifier,
      base::Time last_active_time) {
    std::unique_ptr<web::FakeWebState> web_state =
        std::make_unique<web::FakeWebState>(unique_identifier);
    web_state->SetLastActiveTime(last_active_time);
    return web_state;
  }

  void AddActiveTab(WebStateList* web_state_list) {
    web_state_list->InsertWebState(
        CreateTab(web::WebStateID::NewUnique(), base::Time::Now()),
        WebStateList::InsertionParams::Automatic().Activate());
  }

  void AddInactiveTab(WebStateList* web_state_list,
                      base::TimeDelta delta,
                      bool pinned = false) {
    web_state_list->InsertWebState(
        CreateTab(web::WebStateID::NewUnique(), base::Time::Now() - delta),
        WebStateList::InsertionParams::Automatic().Activate().Pinned(pinned));
  }

  void CheckOrder(WebStateList* web_state_list,
                  std::vector<int> expected_inactivity_days) {
    for (int index = 0; index < web_state_list->count(); index++) {
      web::WebState* current_web_state = web_state_list->GetWebStateAt(index);
      int time_since_last_activation =
          (base::Time::Now() - current_web_state->GetLastActiveTime()).InDays();
      ASSERT_LT(index, static_cast<int>(expected_inactivity_days.size()));
      EXPECT_EQ(time_since_last_activation, expected_inactivity_days[index]);
    }
  }
};

// Ensure that the active tab in the active tab list with date set at "Now" is
// not added to the inactive tab list.
TEST_F(InactiveTabsUtilsTest, ActiveTabStaysActive) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add a new active tab in the active browser.
  AddActiveTab(active_web_state_list);

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateActiveToInactive", 0, 1);
}

// Ensure that inactive tabs are moved from the active tab list to the inactive
// tab list.
TEST_F(InactiveTabsUtilsTest, InactiveTabAreMovedFromActiveList) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add a new inactive tab (10 days with no activity) in the active browser.
  AddInactiveTab(active_web_state_list, base::Days(10));

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 1);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateActiveToInactive", 0, 1);
}

// Ensure there is no active tab in the inactive tab list.
TEST_F(InactiveTabsUtilsTest, ActiveTabAreMovedFromInactiveList) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add a new active tab in the inactive browser.
  AddActiveTab(inactive_web_state_list);

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 1);

  MoveTabsFromInactiveToActive(browser_inactive_.get(), browser_active_.get());

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateInactiveToActive", 0, 1);
}

// Ensure that inactive tab stay in inactive list.
TEST_F(InactiveTabsUtilsTest, InactiveTabStaysInactive) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add a new inactive tab (10 days without activity) in the inactive browser.
  AddInactiveTab(inactive_web_state_list, base::Days(10));

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 1);

  MoveTabsFromInactiveToActive(browser_inactive_.get(), browser_active_.get());

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 1);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateInactiveToActive", 0, 1);
}

// Restore all inactive tab.
TEST_F(InactiveTabsUtilsTest, RestoreAllInactive) {
  // RestoreAllInactive checks that it is called when the feature is disabled,
  // either via the flag, or via the user pref. Disable in both places.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kTabInactivityThreshold);
  local_state()->SetInteger(prefs::kInactiveTabsTimeThreshold, -1);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add a new inactive tab (10 days without activity) in the inactive browser.
  AddInactiveTab(inactive_web_state_list, base::Days(10));

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 1);

  RestoreAllInactiveTabs(browser_inactive_.get(), browser_active_.get());

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnRestoreAllInactive", 0, 1);
}

// Ensure that all moving functions are working with complicated lists (multiple
// tabs, un-ordered, pinned tabs).
TEST_F(InactiveTabsUtilsTest, ComplicatedMove) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitWithFeaturesAndParameters(
      {/* Enabled features */
       {kTabInactivityThreshold, {parameters}}},
      {/* Disabled features */});

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add a new inactive and active tabs in the inactive browser.
  AddActiveTab(inactive_web_state_list);
  AddInactiveTab(inactive_web_state_list, base::Days(10));
  AddInactiveTab(inactive_web_state_list, base::Days(30));
  AddInactiveTab(inactive_web_state_list, base::Days(2));
  AddInactiveTab(inactive_web_state_list, base::Days(16));
  AddActiveTab(inactive_web_state_list);

  // Add a new inactive and active tabs in the active browser.
  AddInactiveTab(active_web_state_list, base::Days(22));
  AddActiveTab(active_web_state_list);
  AddInactiveTab(active_web_state_list, base::Days(9));
  AddActiveTab(active_web_state_list);
  AddInactiveTab(active_web_state_list, base::Days(18), /*pinned=*/true);
  AddInactiveTab(active_web_state_list, base::Days(3));

  EXPECT_EQ(active_web_state_list->count(), 6);
  EXPECT_EQ(inactive_web_state_list->count(), 6);

  // Pinned first (18) and then creation order (22, 0, 9, 0, 3).
  std::vector<int> expected_active_last_activity_order_before = {18, 22, 0,
                                                                 9,  0,  3};
  CheckOrder(active_web_state_list, expected_active_last_activity_order_before);
  // Creation order.
  std::vector<int> expected_inactive_last_activity_order_before = {0, 10, 30,
                                                                   2, 16, 0};
  CheckOrder(inactive_web_state_list,
             expected_inactive_last_activity_order_before);

  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());

  EXPECT_EQ(active_web_state_list->count(), 4);
  EXPECT_EQ(inactive_web_state_list->count(), 8);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateActiveToInactive", 0, 1);

  // "old" inactive first (0, 10, 30, 2, 16, 0) and finally "new" inactive from
  // active list (22, 9).
  std::vector<int> expected_inactive_last_activity_order1 = {0,  10, 30, 2,
                                                             16, 0,  22, 9};
  CheckOrder(inactive_web_state_list, expected_inactive_last_activity_order1);

  // Pinned first (18) and then active (0, 0, 3).
  std::vector<int> expected_active_last_activity_order1 = {18, 0, 0, 3};
  CheckOrder(active_web_state_list, expected_active_last_activity_order1);

  MoveTabsFromInactiveToActive(browser_inactive_.get(), browser_active_.get());

  EXPECT_EQ(active_web_state_list->count(), 7);
  EXPECT_EQ(inactive_web_state_list->count(), 5);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateInactiveToActive", 0, 1);

  // All active (< 8 days of inactivity) are removed.
  std::vector<int> expected_inactive_last_activity_order2 = {10, 30, 16, 22, 9};
  CheckOrder(inactive_web_state_list, expected_inactive_last_activity_order2);

  // Pinned first (18) then "new" active from the inactive (0, 2, 0) then "old"
  // active (0, 0, 3).
  std::vector<int> expected_active_last_activity_order2 = {18, 0, 2, 0,
                                                           0,  0, 3};
  CheckOrder(active_web_state_list, expected_active_last_activity_order2);
}

// Ensure that restore function is working with complicated lists (multiple
// tabs, un-ordered, pinned tabs).
TEST_F(InactiveTabsUtilsTest, ComplicatedRestore) {
  // RestoreAllInactive checks that it is called when the feature is disabled,
  // either via the flag, or via the user pref. Disable in both places.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kTabInactivityThreshold);
  local_state()->SetInteger(prefs::kInactiveTabsTimeThreshold, -1);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add a new inactive and active tabs in the inactive browser.
  AddActiveTab(inactive_web_state_list);
  AddInactiveTab(inactive_web_state_list, base::Days(10));
  AddInactiveTab(inactive_web_state_list, base::Days(30));
  AddInactiveTab(inactive_web_state_list, base::Days(2));
  AddInactiveTab(inactive_web_state_list, base::Days(16));
  AddActiveTab(inactive_web_state_list);

  // Add pinned and active tab in the active browser.
  AddActiveTab(active_web_state_list);
  AddInactiveTab(active_web_state_list, base::Days(18), /*pinned=*/true);

  EXPECT_EQ(active_web_state_list->count(), 2);
  EXPECT_EQ(inactive_web_state_list->count(), 6);

  RestoreAllInactiveTabs(browser_inactive_.get(), browser_active_.get());

  EXPECT_EQ(active_web_state_list->count(), 8);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Pinned first (18) then inactive (0, 10, 30, 2, 16, 0) and finally active
  // (0).
  std::vector<int> expected_last_activity_order = {18, 0, 10, 30, 2, 16, 0, 0};
  CheckOrder(active_web_state_list, expected_last_activity_order);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnRestoreAllInactive", 0, 1);
}

TEST_F(InactiveTabsUtilsTest, DoNotMoveNTPInInactive) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  // Needed to use the NewTabPageTabHelper and ensure that the tab is an NTP.
  std::unique_ptr<web::FakeNavigationManager> fake_navigation_manager =
      std::make_unique<web::FakeNavigationManager>();

  std::unique_ptr<web::NavigationItem> pending_item =
      web::NavigationItem::Create();
  pending_item->SetURL(GURL(kChromeUIAboutNewTabURL));
  fake_navigation_manager->SetPendingItem(pending_item.get());

  // Create a New Tab Page (NTP) tab with the last activity at 30 days ago.
  std::unique_ptr<web::FakeWebState> fake_web_state =
      std::make_unique<web::FakeWebState>();
  GURL url(kChromeUINewTabURL);
  fake_web_state->SetVisibleURL(url);
  fake_web_state->SetNavigationManager(std::move(fake_navigation_manager));
  fake_web_state->SetLastActiveTime(base::Time::Now() - base::Days(30));
  fake_web_state->SetBrowserState(browser_state_.get());

  // Ensure this is an ntp web state.
  id delegate = OCMProtocolMock(@protocol(NewTabPageTabHelperDelegate));
  NewTabPageTabHelper::CreateForWebState(fake_web_state.get());
  NewTabPageTabHelper* ntp_helper =
      NewTabPageTabHelper::FromWebState(fake_web_state.get());
  ntp_helper->SetDelegate(delegate);
  ASSERT_TRUE(ntp_helper->IsActive());

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add the created ntp in the active browser.
  active_web_state_list->InsertWebState(
      std::move(fake_web_state),
      WebStateList::InsertionParams::Automatic().Activate());

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateActiveToInactive", 0, 1);
}

TEST_F(InactiveTabsUtilsTest, EnsurePreferencePriority) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  // Test that flags are taken into account instead of pref as we set the
  // preference default value.
  local_state()->SetInteger(prefs::kInactiveTabsTimeThreshold, 0);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  // Add tabs in the active browser.
  AddInactiveTab(active_web_state_list, base::Days(3));
  AddInactiveTab(active_web_state_list, base::Days(10));
  AddInactiveTab(active_web_state_list, base::Days(30));

  EXPECT_EQ(active_web_state_list->count(), 3);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 2);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateActiveToInactive", 0, 1);

  std::vector<int> expected_inactive_order = {10, 30};
  CheckOrder(inactive_web_state_list, expected_inactive_order);

  // Set the preference to 14.
  local_state()->SetInteger(prefs::kInactiveTabsTimeThreshold, 14);
  MoveTabsFromInactiveToActive(browser_inactive_.get(), browser_active_.get());

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateInactiveToActive", 0, 1);

  EXPECT_EQ(active_web_state_list->count(), 2);
  EXPECT_EQ(inactive_web_state_list->count(), 1);

  std::vector<int> expected_active_order = {10, 3};
  CheckOrder(active_web_state_list, expected_active_order);
}

// Checks that Inactive Tabs migration method RestoreAllInactiveTabs filters out
// duplicates across browsers.
TEST_F(InactiveTabsUtilsTest, RestoreAllInactiveTabsRemovesCrossDuplicates) {
  // RestoreAllInactive checks that it is called when the feature is disabled,
  // either via the flag, or via the user pref. Disable in both places.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kTabInactivityThreshold);
  local_state()->SetInteger(prefs::kInactiveTabsTimeThreshold, -1);

  // Create known identifiers and last_active_time.
  const web::WebStateID unique_identifier = web::WebStateID::NewUnique();
  const base::Time last_active_time = base::Time::Now();

  // Create and insert an active tab with known identifiers.
  browser_active_->GetWebStateList()->InsertWebState(
      CreateTab(unique_identifier, last_active_time),
      WebStateList::InsertionParams::Automatic().Activate());

  // Create and insert an inactive tab with the same identifiers.
  browser_inactive_->GetWebStateList()->InsertWebState(
      CreateTab(unique_identifier, last_active_time),
      WebStateList::InsertionParams::Automatic().Activate());

  // Migrate back all inactive tabs to the active browser.
  RestoreAllInactiveTabs(browser_inactive_.get(), browser_active_.get());

  EXPECT_EQ(browser_active_->GetWebStateList()->count(), 1);
  EXPECT_EQ(browser_inactive_->GetWebStateList()->count(), 0);

  // Expect a log of 1 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnRestoreAllInactive", 1, 1);
}

// Checks that Inactive Tabs migration method MoveTabsFromInactiveToActive
// filters out duplicates across browsers.
TEST_F(InactiveTabsUtilsTest,
       MoveTabsFromInactiveToActiveRemovesCrossDuplicates) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  // Create known identifiers and last_active_time.
  const web::WebStateID unique_identifier = web::WebStateID::NewUnique();
  const base::Time last_active_time = base::Time::Now();

  // Create and insert an active tab with known identifiers.
  browser_active_->GetWebStateList()->InsertWebState(
      CreateTab(unique_identifier, last_active_time),
      WebStateList::InsertionParams::Automatic().Activate());

  // Create and insert an inactive tab with the same identifiers.
  browser_inactive_->GetWebStateList()->InsertWebState(
      CreateTab(unique_identifier, last_active_time),
      WebStateList::InsertionParams::Automatic().Activate());

  // Migrate back all inactive tabs to the active browser.
  MoveTabsFromInactiveToActive(browser_inactive_.get(), browser_active_.get());

  EXPECT_EQ(browser_active_->GetWebStateList()->count(), 1);
  EXPECT_EQ(browser_inactive_->GetWebStateList()->count(), 0);

  // Expect a log of 1 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateInactiveToActive", 1, 1);
}

// Checks that Inactive Tabs migration method MoveTabsFromActiveToInactive
// filters out duplicates across browsers.
TEST_F(InactiveTabsUtilsTest,
       MoveTabsFromActiveToInactiveRemovesCrossDuplicates) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  // Create known identifiers and last_active_time.
  const web::WebStateID unique_identifier = web::WebStateID::NewUnique();
  const base::Time last_active_time = base::Time::Now() - base::Days(10);

  // Create and insert an active tab with known identifiers.
  browser_active_->GetWebStateList()->InsertWebState(
      CreateTab(unique_identifier, last_active_time),
      WebStateList::InsertionParams::Automatic().Activate());

  // Create and insert an inactive tab with the same identifiers.
  browser_inactive_->GetWebStateList()->InsertWebState(
      CreateTab(unique_identifier, last_active_time),
      WebStateList::InsertionParams::Automatic().Activate());

  // Migrate back all inactive tabs to the active browser.
  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());

  EXPECT_EQ(browser_active_->GetWebStateList()->count(), 0);
  EXPECT_EQ(browser_inactive_->GetWebStateList()->count(), 1);

  // Expect a log of 1 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateActiveToInactive", 1, 1);
}

TEST_F(InactiveTabsUtilsTest, DoNotMoveTabInGroupToInactive) {
  // No inactive tabs on iPad.
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  std::map<std::string, std::string> parameters;
  parameters[kTabInactivityThresholdParameterName] =
      kTabInactivityThresholdOneWeekParam;
  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
                                                  parameters);

  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();

  EXPECT_EQ(active_web_state_list->count(), 0);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  AddInactiveTab(active_web_state_list, base::Days(10));
  AddInactiveTab(active_web_state_list, base::Days(12));
  AddInactiveTab(active_web_state_list, base::Days(15));

  EXPECT_EQ(active_web_state_list->count(), 3);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  active_web_state_list->CreateGroup({0}, {}, TabGroupId::GenerateNew());

  EXPECT_EQ(active_web_state_list->count(), 3);
  EXPECT_EQ(inactive_web_state_list->count(), 0);

  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());

  EXPECT_EQ(active_web_state_list->count(), 1);
  EXPECT_EQ(inactive_web_state_list->count(), 2);

  // Expect a log of 0 duplicate.
  histogram_tester_.ExpectUniqueSample(
      "Tabs.DroppedDuplicatesCountOnMigrateActiveToInactive", 0, 1);
}