chromium/ios/chrome/browser/shared/model/web_state_list/tab_utils_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/shared/model/web_state_list/tab_utils.h"

#import "base/test/scoped_feature_list.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/test/fake_web_state_list_delegate.h"
#import "ios/chrome/browser/shared/model/web_state_list/test/web_state_list_builder_from_description.h"
#import "ios/chrome/browser/shared/public/features/features.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"

class TabUtilsTest : public PlatformTest {
 public:
  TabUtilsTest() {
    TestChromeBrowserState::Builder browser_state_builder;
    browser_state_ = std::move(browser_state_builder).Build();
    browser_ = std::make_unique<TestBrowser>(
        browser_state_.get(), std::make_unique<FakeWebStateListDelegate>());
    web_state_list_ = browser_->GetWebStateList();
  }

  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<TestBrowser> browser_;
  raw_ptr<WebStateList> web_state_list_;
};

// Tests that `MoveWebStateWithIdentifierToInsertionParams` correctly moves
// webStates to the desired `insertion_params` with one group.
TEST_F(TabUtilsTest, MoveWebStateWithIdentifierToInsertionParams_oneGroup) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {kTabGroupsInGrid, kTabGroupsIPad, kModernTabStrip}, {});

  WebStateListBuilderFromDescription builder(web_state_list_);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| a [ 0 b c ] d"));

  const TabGroup* group = builder.GetTabGroupForIdentifier('0');

  // Move "A" in the group at the first position.
  auto insertion_params =
      WebStateList::InsertionParams::AtIndex(0).InGroup(group);
  web::WebStateID identifier_a =
      builder.GetWebStateForIdentifier('a')->GetUniqueIdentifier();
  MoveWebStateWithIdentifierToInsertionParams(identifier_a, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| [ 0 a b c ] d", builder.GetWebStateListDescription());

  // Move "A" out of the group at the first position.
  insertion_params = WebStateList::InsertionParams::AtIndex(0);
  MoveWebStateWithIdentifierToInsertionParams(identifier_a, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| a [ 0 b c ] d", builder.GetWebStateListDescription());

  // Move "D" after "A".
  insertion_params = WebStateList::InsertionParams::AtIndex(1);
  web::WebStateID identifier_d =
      builder.GetWebStateForIdentifier('d')->GetUniqueIdentifier();
  MoveWebStateWithIdentifierToInsertionParams(identifier_d, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| a d [ 0 b c ]", builder.GetWebStateListDescription());

  // Move "D" in the group after "B".
  insertion_params = WebStateList::InsertionParams::AtIndex(2).InGroup(group);
  MoveWebStateWithIdentifierToInsertionParams(identifier_d, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| a [ 0 b d c ]", builder.GetWebStateListDescription());

  // Move "D" in the group after "C".
  insertion_params = WebStateList::InsertionParams::AtIndex(3).InGroup(group);
  MoveWebStateWithIdentifierToInsertionParams(identifier_d, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| a [ 0 b c d ]", builder.GetWebStateListDescription());
}

// Tests that `MoveWebStateWithIdentifierToInsertionParams` correctly moves
// webStates to the desired `insertion_params` with multiple groups.
TEST_F(TabUtilsTest,
       MoveWebStateWithIdentifierToInsertionParams_multipleGroups) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {kTabGroupsInGrid, kTabGroupsIPad, kModernTabStrip}, {});

  WebStateListBuilderFromDescription builder(web_state_list_);
  ASSERT_TRUE(
      builder.BuildWebStateListFromDescription("| a [ 0 b c ] d [ 1 e f g ]"));

  const TabGroup* group_1 = builder.GetTabGroupForIdentifier('0');
  const TabGroup* group_2 = builder.GetTabGroupForIdentifier('1');

  // Move "A" in the first group at the first position.
  auto insertion_params =
      WebStateList::InsertionParams::AtIndex(0).InGroup(group_1);
  web::WebStateID identifier_a =
      builder.GetWebStateForIdentifier('a')->GetUniqueIdentifier();
  MoveWebStateWithIdentifierToInsertionParams(identifier_a, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| [ 0 a b c ] d [ 1 e f g ]",
            builder.GetWebStateListDescription());

  // Move "A" in the second group at the first position.
  insertion_params = WebStateList::InsertionParams::AtIndex(3).InGroup(group_2);
  identifier_a = builder.GetWebStateForIdentifier('a')->GetUniqueIdentifier();
  MoveWebStateWithIdentifierToInsertionParams(identifier_a, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| [ 0 b c ] d [ 1 a e f g ]",
            builder.GetWebStateListDescription());

  // Move "A" after "D".
  insertion_params = WebStateList::InsertionParams::AtIndex(3);
  identifier_a = builder.GetWebStateForIdentifier('a')->GetUniqueIdentifier();
  MoveWebStateWithIdentifierToInsertionParams(identifier_a, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ true);
  EXPECT_EQ("| [ 0 b c ] d a [ 1 e f g ]",
            builder.GetWebStateListDescription());
}

// Tests that `MoveWebStateWithIdentifierToInsertionParams` correctly moves
// webStates to the desired `insertion_params` when the item is from another
// collection view.
//
// If from_same_collection is `false and and the sourceWebStateIndex is before
// the first tab group index, increase the `destinationWebStateIndex` by one.
// This is needed because, the destination source is not correclty calculated by
// the collection view.
TEST_F(TabUtilsTest,
       MoveWebStateWithIdentifierToInsertionParams_notSameCollection) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {kTabGroupsInGrid, kTabGroupsIPad, kModernTabStrip}, {});

  const int increase_index = 1;

  WebStateListBuilderFromDescription builder(web_state_list_);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| a [ 0 b c ] d"));

  const TabGroup* group = builder.GetTabGroupForIdentifier('0');

  // Move "A" in the group at the first position.
  auto insertion_params =
      WebStateList::InsertionParams::AtIndex(0 + increase_index).InGroup(group);
  web::WebStateID identifier_a =
      builder.GetWebStateForIdentifier('a')->GetUniqueIdentifier();
  MoveWebStateWithIdentifierToInsertionParams(identifier_a, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ false);
  EXPECT_EQ("| [ 0 a b c ] d", builder.GetWebStateListDescription());

  // Move "A" out of the group at the first position.
  insertion_params = WebStateList::InsertionParams::AtIndex(0);
  MoveWebStateWithIdentifierToInsertionParams(identifier_a, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ false);
  EXPECT_EQ("| a [ 0 b c ] d", builder.GetWebStateListDescription());

  // Move "D" after "A".
  insertion_params = WebStateList::InsertionParams::AtIndex(1);
  web::WebStateID identifier_d =
      builder.GetWebStateForIdentifier('d')->GetUniqueIdentifier();
  MoveWebStateWithIdentifierToInsertionParams(identifier_d, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ false);
  EXPECT_EQ("| a d [ 0 b c ]", builder.GetWebStateListDescription());

  // Move "D" in the group after "B".
  insertion_params =
      WebStateList::InsertionParams::AtIndex(2 + increase_index).InGroup(group);
  MoveWebStateWithIdentifierToInsertionParams(identifier_d, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ false);
  EXPECT_EQ("| a [ 0 b d c ]", builder.GetWebStateListDescription());

  // Move "D" in the group after "C".
  insertion_params =
      WebStateList::InsertionParams::AtIndex(3 + increase_index).InGroup(group);
  MoveWebStateWithIdentifierToInsertionParams(identifier_d, insertion_params,
                                              web_state_list_,
                                              /*from_same_collection*/ false);
  EXPECT_EQ("| a [ 0 b c d ]", builder.GetWebStateListDescription());
}