chromium/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util_unittest.mm

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

#import "ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h"

#import "base/test/scoped_feature_list.h"
#import "components/saved_tab_groups/mock_tab_group_sync_service.h"
#import "components/tab_groups/tab_group_id.h"
#import "ios/chrome/browser/saved_tab_groups/model/tab_group_local_update_observer.h"
#import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/tab_group.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/shared/public/features/features.h"
#import "ios/chrome/browser/snapshots/model/snapshot_browser_agent.h"
#import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/fakes/fake_navigation_context.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/gtest_mac.h"
#import "testing/platform_test.h"

namespace tab_groups {
namespace utils {

namespace {

// Creates a MockTabGroupSyncService.
std::unique_ptr<KeyedService> CreateMockSyncService(
    web::BrowserState* context) {
  return std::make_unique<::testing::NiceMock<MockTabGroupSyncService>>();
}

// Returns the tab ID for the web state at `index` in `browser`.
web::WebStateID GetTabIDForWebStateAt(int index, Browser* browser) {
  web::WebState* web_state = browser->GetWebStateList()->GetWebStateAt(index);
  return web_state->GetUniqueIdentifier();
}

}  // namespace

class TabGroupSyncUtilTest : public PlatformTest {
 protected:
  TabGroupSyncUtilTest() {
    TestChromeBrowserState::Builder test_browser_state_builder;
    test_browser_state_builder.AddTestingFactory(
        TabGroupSyncServiceFactory::GetInstance(),
        base::BindRepeating(&CreateMockSyncService));
    chrome_browser_state_ = std::move(test_browser_state_builder).Build();

    mock_service_ = static_cast<MockTabGroupSyncService*>(
        TabGroupSyncServiceFactory::GetForBrowserState(
            chrome_browser_state_.get()));

    browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
    other_browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());

    browser_list_ =
        BrowserListFactory::GetForBrowserState(chrome_browser_state_.get());
    local_observer_ = std::make_unique<TabGroupLocalUpdateObserver>(
        browser_list_.get(), mock_service_);

    browser_list_->AddBrowser(browser_.get());
    browser_list_->AddBrowser(other_browser_.get());

    SnapshotBrowserAgent::CreateForBrowser(browser_.get());
    SnapshotBrowserAgent::CreateForBrowser(other_browser_.get());
  }

  void SetUp() override {
    feature_list_.InitWithFeatures(
        {kTabGroupsInGrid, kTabGroupsIPad, kModernTabStrip, kTabGroupSync}, {});
    AppendNewWebState(browser_.get());
    AppendNewWebState(browser_.get());
    AppendNewWebState(browser_.get());
  }

  // Appends a new web state in the web state list of `browser`.
  web::FakeWebState* AppendNewWebState(Browser* browser) {
    auto fake_web_state = std::make_unique<web::FakeWebState>();
    web::FakeWebState* inserted_web_state = fake_web_state.get();
    SnapshotTabHelper::CreateForWebState(inserted_web_state);
    browser->GetWebStateList()->InsertWebState(
        std::move(fake_web_state),
        WebStateList::InsertionParams::Automatic().Activate());
    return inserted_web_state;
  }

  base::test::ScopedFeatureList feature_list_;
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
  std::unique_ptr<Browser> browser_;
  std::unique_ptr<Browser> other_browser_;
  raw_ptr<BrowserList> browser_list_;
  raw_ptr<MockTabGroupSyncService> mock_service_;
  std::unique_ptr<TabGroupLocalUpdateObserver> local_observer_;
};

// Tests that a tab group with one tab is moved from one regular browser to
// another browser.
TEST_F(TabGroupSyncUtilTest, TestMoveTabGroupOneTabAcrossRegularBrowsers) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateList* other_web_state_list = other_browser_->GetWebStateList();

  // Create a group of two tabs.
  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  TabGroupVisualData visual_data =
      TabGroupVisualData(u"Group", TabGroupColorId::kGrey);
  const TabGroup* tab_group = web_state_list->CreateGroup(
      {2}, TabGroupVisualData(visual_data), tab_group_id);

  web::WebStateID tab_id = GetTabIDForWebStateAt(2, browser_.get());
  ASSERT_EQ(3, web_state_list->count());
  ASSERT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(2));

  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(1);
  local_observer_->SetSyncUpdatePaused(true);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id)).Times(0);

  // Move the group.
  MoveTabGroupToBrowser(tab_group, other_browser_.get(), 0);

  const TabGroup* other_group = other_web_state_list->GetGroupOfWebStateAt(0);
  ASSERT_TRUE(other_group);
  EXPECT_EQ(1, other_group->range().count());
  EXPECT_EQ(tab_group_id, other_group->tab_group_id());
  EXPECT_EQ(visual_data, other_group->visual_data());
  EXPECT_EQ(2, web_state_list->count());
  EXPECT_EQ(1, other_web_state_list->count());
  EXPECT_NE(tab_id, GetTabIDForWebStateAt(1, browser_.get()));
  EXPECT_EQ(tab_id, GetTabIDForWebStateAt(0, other_browser_.get()));
}

// Tests that a tab group with multiple tabs is moved from one regular browser
// to another browser.
TEST_F(TabGroupSyncUtilTest, TestMoveTabGroupMutipleTabsAcrossRegularBrowsers) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateList* other_web_state_list = other_browser_->GetWebStateList();

  // Create a group of two tabs.
  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  TabGroupVisualData visual_data =
      TabGroupVisualData(u"Group", TabGroupColorId::kGrey);
  const TabGroup* tab_group = web_state_list->CreateGroup(
      {0, 1}, TabGroupVisualData(visual_data), tab_group_id);
  web::WebStateID tab_id_0 = GetTabIDForWebStateAt(0, browser_.get());
  web::WebStateID tab_id_1 = GetTabIDForWebStateAt(1, browser_.get());
  ASSERT_EQ(3, web_state_list->count());
  ASSERT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(0));

  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(1);
  local_observer_->SetSyncUpdatePaused(true);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id)).Times(0);

  // Move the group.
  MoveTabGroupToBrowser(tab_group, other_browser_.get(), 0);

  const TabGroup* other_group = other_web_state_list->GetGroupOfWebStateAt(0);
  ASSERT_TRUE(other_group);
  EXPECT_EQ(2, other_group->range().count());
  EXPECT_EQ(tab_group_id, other_group->tab_group_id());
  EXPECT_EQ(visual_data, other_group->visual_data());
  EXPECT_EQ(1, web_state_list->count());
  EXPECT_EQ(2, other_web_state_list->count());
  EXPECT_EQ(tab_id_0, GetTabIDForWebStateAt(0, other_browser_.get()));
  EXPECT_EQ(tab_id_1, GetTabIDForWebStateAt(1, other_browser_.get()));
}

// Tests that a tab group with multiple tabs is moved from one regular browser
// to another browser.
TEST_F(TabGroupSyncUtilTest, TestMoveTabGroupsAcrossRegularBrowsers) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateList* other_web_state_list = other_browser_->GetWebStateList();

  // Create 2 groups.
  TabGroupId tab_group_id_0 = TabGroupId::GenerateNew();
  TabGroupId tab_group_id_1 = TabGroupId::GenerateNew();
  TabGroupVisualData visual_data =
      TabGroupVisualData(u"Group", TabGroupColorId::kGrey);
  const TabGroup* tab_group_0 = web_state_list->CreateGroup(
      {0}, TabGroupVisualData(visual_data), tab_group_id_0);
  const TabGroup* tab_group_1 = web_state_list->CreateGroup(
      {1}, TabGroupVisualData(visual_data), tab_group_id_1);
  web::WebStateID tab_id_0 = GetTabIDForWebStateAt(0, browser_.get());
  web::WebStateID tab_id_1 = GetTabIDForWebStateAt(1, browser_.get());
  ASSERT_EQ(3, web_state_list->count());
  ASSERT_EQ(tab_group_0, web_state_list->GetGroupOfWebStateAt(0));
  ASSERT_EQ(tab_group_1, web_state_list->GetGroupOfWebStateAt(1));

  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(2);
  local_observer_->SetSyncUpdatePaused(true);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id_0)).Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id_1)).Times(0);

  // Move groups.
  MoveTabGroupToBrowser(tab_group_0, other_browser_.get(), 0);
  MoveTabGroupToBrowser(tab_group_1, other_browser_.get(), 1);

  const TabGroup* other_group_0 = other_web_state_list->GetGroupOfWebStateAt(0);
  const TabGroup* other_group_1 = other_web_state_list->GetGroupOfWebStateAt(1);
  ASSERT_TRUE(other_group_0);
  ASSERT_TRUE(other_group_1);
  EXPECT_EQ(1, other_group_0->range().count());
  EXPECT_EQ(1, other_group_1->range().count());
  EXPECT_EQ(tab_group_id_0, other_group_0->tab_group_id());
  EXPECT_EQ(tab_group_id_1, other_group_1->tab_group_id());
  EXPECT_EQ(visual_data, other_group_0->visual_data());
  EXPECT_EQ(visual_data, other_group_1->visual_data());
  EXPECT_EQ(1, web_state_list->count());
  EXPECT_EQ(2, other_web_state_list->count());
  EXPECT_EQ(tab_id_0, GetTabIDForWebStateAt(0, other_browser_.get()));
  EXPECT_EQ(tab_id_1, GetTabIDForWebStateAt(1, other_browser_.get()));
}

// Tests that a tab group with multiple tabs is moved from one regular browser
// to another browser with tab group sync disabled.
TEST_F(TabGroupSyncUtilTest,
       TestMoveTabGroupsAcrossRegularBrowsersSyncDisabled) {
  feature_list_.Reset();
  feature_list_.InitAndDisableFeature(kTabGroupSync);

  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateList* other_web_state_list = other_browser_->GetWebStateList();

  // Create 2 groups.
  TabGroupId tab_group_id_0 = TabGroupId::GenerateNew();
  TabGroupId tab_group_id_1 = TabGroupId::GenerateNew();
  TabGroupVisualData visual_data =
      TabGroupVisualData(u"Group", TabGroupColorId::kGrey);
  const TabGroup* tab_group_0 = web_state_list->CreateGroup(
      {0}, TabGroupVisualData(visual_data), tab_group_id_0);
  const TabGroup* tab_group_1 = web_state_list->CreateGroup(
      {1}, TabGroupVisualData(visual_data), tab_group_id_1);
  web::WebStateID tab_id_0 = GetTabIDForWebStateAt(0, browser_.get());
  web::WebStateID tab_id_1 = GetTabIDForWebStateAt(1, browser_.get());
  ASSERT_EQ(3, web_state_list->count());
  ASSERT_EQ(tab_group_0, web_state_list->GetGroupOfWebStateAt(0));
  ASSERT_EQ(tab_group_1, web_state_list->GetGroupOfWebStateAt(1));

  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id_0)).Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id_1)).Times(0);

  // Move groups.
  MoveTabGroupToBrowser(tab_group_0, other_browser_.get(), 0);
  MoveTabGroupToBrowser(tab_group_1, other_browser_.get(), 1);

  const TabGroup* other_group_0 = other_web_state_list->GetGroupOfWebStateAt(0);
  const TabGroup* other_group_1 = other_web_state_list->GetGroupOfWebStateAt(1);
  ASSERT_TRUE(other_group_0);
  ASSERT_TRUE(other_group_1);
  EXPECT_EQ(1, other_group_0->range().count());
  EXPECT_EQ(1, other_group_1->range().count());
  EXPECT_EQ(tab_group_id_0, other_group_0->tab_group_id());
  EXPECT_EQ(tab_group_id_1, other_group_1->tab_group_id());
  EXPECT_EQ(visual_data, other_group_0->visual_data());
  EXPECT_EQ(visual_data, other_group_1->visual_data());
  EXPECT_EQ(1, web_state_list->count());
  EXPECT_EQ(2, other_web_state_list->count());
  EXPECT_EQ(tab_id_0, GetTabIDForWebStateAt(0, other_browser_.get()));
  EXPECT_EQ(tab_id_1, GetTabIDForWebStateAt(1, other_browser_.get()));
}

// Tests that moving a tab group to its owning browser with the same destination
// index is a no-op.
TEST_F(TabGroupSyncUtilTest, MoveTabGroupToItsOwningBrowser_SameIndex) {
  WebStateList* web_state_list = browser_->GetWebStateList();

  // Create a group of two tabs.
  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  TabGroupVisualData visual_data =
      TabGroupVisualData(u"Group", TabGroupColorId::kGrey);
  const TabGroup* tab_group = web_state_list->CreateGroup(
      {0, 1}, TabGroupVisualData(visual_data), tab_group_id);
  web::WebStateID tab_id_0 = GetTabIDForWebStateAt(0, browser_.get());
  web::WebStateID tab_id_1 = GetTabIDForWebStateAt(1, browser_.get());
  ASSERT_EQ(3, web_state_list->count());
  ASSERT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(0));

  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id)).Times(0);

  // Move the group.
  MoveTabGroupToBrowser(tab_group, browser_.get(), 0);

  EXPECT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(0));
  EXPECT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(1));
  EXPECT_EQ(nullptr, web_state_list->GetGroupOfWebStateAt(2));
  EXPECT_EQ(2, tab_group->range().count());
  EXPECT_EQ(tab_group_id, tab_group->tab_group_id());
  EXPECT_EQ(visual_data, tab_group->visual_data());
  EXPECT_EQ(3, web_state_list->count());
  EXPECT_EQ(tab_id_0, GetTabIDForWebStateAt(0, browser_.get()));
  EXPECT_EQ(tab_id_1, GetTabIDForWebStateAt(1, browser_.get()));
}

// Tests that moving a tab group to its owning browser to a position on its
// right moves the group accordingly.
TEST_F(TabGroupSyncUtilTest, MoveTabGroupToItsOwningBrowser_MovingRight) {
  WebStateList* web_state_list = browser_->GetWebStateList();

  // Create a group of two tabs.
  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  TabGroupVisualData visual_data =
      TabGroupVisualData(u"Group", TabGroupColorId::kGrey);
  const TabGroup* tab_group = web_state_list->CreateGroup(
      {0, 1}, TabGroupVisualData(visual_data), tab_group_id);
  web::WebStateID tab_id_0 = GetTabIDForWebStateAt(0, browser_.get());
  web::WebStateID tab_id_1 = GetTabIDForWebStateAt(1, browser_.get());
  ASSERT_EQ(3, web_state_list->count());
  ASSERT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(0));

  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id)).Times(0);

  // Move the group.
  MoveTabGroupToBrowser(tab_group, browser_.get(), 3);

  EXPECT_EQ(nullptr, web_state_list->GetGroupOfWebStateAt(0));
  EXPECT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(1));
  EXPECT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(2));
  EXPECT_EQ(2, tab_group->range().count());
  EXPECT_EQ(tab_group_id, tab_group->tab_group_id());
  EXPECT_EQ(visual_data, tab_group->visual_data());
  EXPECT_EQ(3, web_state_list->count());
  EXPECT_EQ(tab_id_0, GetTabIDForWebStateAt(1, browser_.get()));
  EXPECT_EQ(tab_id_1, GetTabIDForWebStateAt(2, browser_.get()));
}

// Tests that moving a tab group to its owning browser to a position on its left
// moves the group accordingly.
TEST_F(TabGroupSyncUtilTest, MoveTabGroupToItsOwningBrowser_MovingLeft) {
  WebStateList* web_state_list = browser_->GetWebStateList();

  // Create a group of two tabs.
  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  TabGroupVisualData visual_data =
      TabGroupVisualData(u"Group", TabGroupColorId::kGrey);
  const TabGroup* tab_group = web_state_list->CreateGroup(
      {1, 2}, TabGroupVisualData(visual_data), tab_group_id);
  web::WebStateID tab_id_1 = GetTabIDForWebStateAt(1, browser_.get());
  web::WebStateID tab_id_2 = GetTabIDForWebStateAt(2, browser_.get());
  ASSERT_EQ(3, web_state_list->count());
  ASSERT_EQ(nullptr, web_state_list->GetGroupOfWebStateAt(0));
  ASSERT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(1));
  ASSERT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(2));

  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id)).Times(0);

  // Move the group to the left.
  MoveTabGroupToBrowser(tab_group, browser_.get(), 0);

  EXPECT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(0));
  EXPECT_EQ(tab_group, web_state_list->GetGroupOfWebStateAt(1));
  EXPECT_EQ(2, tab_group->range().count());
  EXPECT_EQ(tab_group_id, tab_group->tab_group_id());
  EXPECT_EQ(visual_data, tab_group->visual_data());
  EXPECT_EQ(3, web_state_list->count());
  EXPECT_EQ(tab_id_1, GetTabIDForWebStateAt(0, browser_.get()));
  EXPECT_EQ(tab_id_2, GetTabIDForWebStateAt(1, browser_.get()));
}

TEST_F(TabGroupSyncUtilTest, ShouldUpdateHistory) {
  std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
  item->SetTransitionType(ui::PAGE_TRANSITION_TYPED);

  auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
  navigation_manager->SetLastCommittedItem(item.get());

  auto web_state = std::make_unique<web::FakeWebState>();
  web_state->SetNavigationManager(std::move(navigation_manager));

  auto browser_state = std::make_unique<web::FakeBrowserState>();
  browser_state->SetOffTheRecord(false);
  web_state->SetBrowserState(browser_state.get());

  web::FakeNavigationContext navigation;
  navigation.SetWebState(web_state.get());
  navigation.SetHasCommitted(true);

  EXPECT_TRUE(ShouldUpdateHistory(&navigation));

  // Off the record navigation.
  browser_state->SetOffTheRecord(true);
  EXPECT_FALSE(ShouldUpdateHistory(&navigation));
  browser_state->SetOffTheRecord(false);

  // Back / forward navigation.
  EXPECT_TRUE(ShouldUpdateHistory(&navigation));
  item->SetTransitionType(ui::PAGE_TRANSITION_FORWARD_BACK);
  EXPECT_FALSE(ShouldUpdateHistory(&navigation));
  item->SetTransitionType(ui::PAGE_TRANSITION_TYPED);

  // Navigation error.
  EXPECT_TRUE(ShouldUpdateHistory(&navigation));
  navigation.SetError([NSError
      errorWithDomain:@"commm"
                 code:200
             userInfo:@{@"Error reason" : @"Invalid Input"}]);
  EXPECT_FALSE(ShouldUpdateHistory(&navigation));
  item->SetTransitionType(ui::PAGE_TRANSITION_TYPED);
}

TEST_F(TabGroupSyncUtilTest, IsSaveableNavigation) {
  std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
  item->SetTransitionType(ui::PAGE_TRANSITION_TYPED);

  auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
  navigation_manager->SetLastCommittedItem(item.get());

  auto web_state = std::make_unique<web::FakeWebState>();
  web_state->SetNavigationManager(std::move(navigation_manager));

  auto browser_state = std::make_unique<web::FakeBrowserState>();
  browser_state->SetOffTheRecord(false);
  web_state->SetBrowserState(browser_state.get());

  web::FakeNavigationContext navigation;
  navigation.SetWebState(web_state.get());

  navigation.SetUrl(GURL("https://google.com"));
  navigation.SetHasCommitted(true);
  navigation.SetPageTransition(ui::PAGE_TRANSITION_LINK);
  navigation.SetHasUserGesture(true);
  navigation.SetIsRendererInitiated(false);
  navigation.SetIsPost(false);
  EXPECT_TRUE(IsSaveableNavigation(&navigation));

  // Invalid URL scheme.
  navigation.SetUrl(GURL("ftp://google.com"));
  EXPECT_FALSE(IsSaveableNavigation(&navigation));

  // URL is empty.
  navigation.SetUrl(GURL());
  EXPECT_FALSE(IsSaveableNavigation(&navigation));

  // URL is chrome:// scheme.
  navigation.SetUrl(GURL("chrome://flags"));
  EXPECT_FALSE(IsSaveableNavigation(&navigation));

  // URL is HTTP.
  navigation.SetUrl(GURL("http://example.com"));
  EXPECT_TRUE(IsSaveableNavigation(&navigation));

  // URL is NTP.
  navigation.SetUrl(GURL("chrome://newtab"));
  EXPECT_TRUE(IsSaveableNavigation(&navigation));

  // Non-committed navigation.
  EXPECT_TRUE(IsSaveableNavigation(&navigation));
  navigation.SetHasCommitted(false);
  EXPECT_FALSE(IsSaveableNavigation(&navigation));
  navigation.SetHasCommitted(true);

  // POST request.
  EXPECT_TRUE(IsSaveableNavigation(&navigation));
  navigation.SetIsPost(true);
  EXPECT_FALSE(IsSaveableNavigation(&navigation));
  navigation.SetIsPost(false);

  // Server redirect.
  EXPECT_TRUE(IsSaveableNavigation(&navigation));
  navigation.SetPageTransition(ui::PAGE_TRANSITION_SERVER_REDIRECT);
  EXPECT_FALSE(IsSaveableNavigation(&navigation));

  // Client redirect.
  navigation.SetPageTransition(ui::PAGE_TRANSITION_CLIENT_REDIRECT);
  EXPECT_FALSE(IsSaveableNavigation(&navigation));
  navigation.SetPageTransition(ui::PAGE_TRANSITION_LINK);

  // Not a main frame navigation.
  EXPECT_TRUE(IsSaveableNavigation(&navigation));
  navigation.SetPageTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
  EXPECT_FALSE(IsSaveableNavigation(&navigation));
  navigation.SetPageTransition(ui::PAGE_TRANSITION_LINK);

  // Not user initiated navigation.
  EXPECT_TRUE(IsSaveableNavigation(&navigation));
  navigation.SetHasUserGesture(false);
  navigation.SetIsRendererInitiated(true);
  EXPECT_FALSE(IsSaveableNavigation(&navigation));

  // Browser initiated navigation.
  navigation.SetIsRendererInitiated(false);
  EXPECT_TRUE(IsSaveableNavigation(&navigation));

  // Off the record navigation.
  EXPECT_TRUE(IsSaveableNavigation(&navigation));
  browser_state->SetOffTheRecord(true);
  EXPECT_FALSE(IsSaveableNavigation(&navigation));
}

}  // namespace utils
}  // namespace tab_groups