chromium/ios/chrome/browser/ui/tab_switcher/tab_strip/coordinator/tab_strip_mediator_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/ui/tab_switcher/tab_strip/coordinator/tab_strip_mediator_utils.h"

#import "base/test/scoped_feature_list.h"
#import "components/saved_tab_groups/mock_tab_group_sync_service.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/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/chrome/browser/snapshots/model/snapshot_browser_agent.h"
#import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_strip/ui/swift.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_state.h"
#import "testing/platform_test.h"

namespace {

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

// Creates a WebState for test purposes.
std::unique_ptr<web::WebState> CreateWebState() {
  auto web_state =
      std::make_unique<web::FakeWebState>(web::WebStateID::NewUnique());
  SnapshotTabHelper::CreateForWebState(web_state.get());
  return std::move(web_state);
}

}  // namespace

// Test fixture for the TabStripMediator.
class TabStripMediatorUtilsTest : public PlatformTest {
 public:
  TabStripMediatorUtilsTest() {
    feature_list_.InitWithFeatures(
        {kTabGroupsInGrid, kTabGroupsIPad, kModernTabStrip, kTabGroupSync}, {});
    TestChromeBrowserState::Builder browser_state_builder;
    browser_state_builder.AddTestingFactory(
        tab_groups::TabGroupSyncServiceFactory::GetInstance(),
        base::BindRepeating(&CreateMockSyncService));
    browser_state_ = std::move(browser_state_builder).Build();
    mock_service_ = static_cast<tab_groups::MockTabGroupSyncService*>(
        tab_groups::TabGroupSyncServiceFactory::GetForBrowserState(
            browser_state_.get()));
    browser_ = std::make_unique<TestBrowser>(
        browser_state_.get(), std::make_unique<FakeWebStateListDelegate>());
    web_state_list_ = browser_->GetWebStateList();
    other_browser_ = std::make_unique<TestBrowser>(
        browser_state_.get(), std::make_unique<FakeWebStateListDelegate>());
    other_web_state_list_ = other_browser_->GetWebStateList();
    BrowserList* browser_list =
        BrowserListFactory::GetForBrowserState(browser_state_.get());
    local_observer_ = std::make_unique<tab_groups::TabGroupLocalUpdateObserver>(
        browser_list, mock_service_);
    browser_list->AddBrowser(browser_.get());
    browser_list->AddBrowser(other_browser_.get());
    SnapshotBrowserAgent::CreateForBrowser(browser_.get());
    SnapshotBrowserAgent::CreateForBrowser(other_browser_.get());
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<TestBrowser> browser_;
  raw_ptr<WebStateList> web_state_list_;
  std::unique_ptr<TestBrowser> other_browser_;
  raw_ptr<WebStateList> other_web_state_list_;
  raw_ptr<tab_groups::MockTabGroupSyncService> mock_service_;
  std::unique_ptr<tab_groups::TabGroupLocalUpdateObserver> local_observer_;
};

// Test that `CreateTabItemIdentifier` returns the correct
// `TabStripItemIdentifier` for a WebState.
TEST_F(TabStripMediatorUtilsTest, CreateTabItemIdentifier) {
  WebStateListBuilderFromDescription builder(web_state_list_.get());
  ASSERT_TRUE(builder.BuildWebStateListFromDescription(
      "| [ 0 a b* ] c [ 1 d e ]", base::BindRepeating(CreateWebState)));

  web::WebState* webstate_a = builder.GetWebStateForIdentifier('a');
  web::WebState* webstate_b = builder.GetWebStateForIdentifier('b');
  web::WebState* webstate_c = builder.GetWebStateForIdentifier('c');
  web::WebState* webstate_d = builder.GetWebStateForIdentifier('d');
  web::WebState* webstate_e = builder.GetWebStateForIdentifier('e');

  TabStripItemIdentifier* webstate_a_item_identifier =
      CreateTabItemIdentifier(webstate_a);
  EXPECT_EQ(webstate_a->GetUniqueIdentifier(),
            webstate_a_item_identifier.tabSwitcherItem.identifier);
  TabStripItemIdentifier* webstate_b_item_identifier =
      CreateTabItemIdentifier(webstate_b);
  EXPECT_EQ(webstate_b->GetUniqueIdentifier(),
            webstate_b_item_identifier.tabSwitcherItem.identifier);
  TabStripItemIdentifier* webstate_c_item_identifier =
      CreateTabItemIdentifier(webstate_c);
  EXPECT_EQ(webstate_c->GetUniqueIdentifier(),
            webstate_c_item_identifier.tabSwitcherItem.identifier);
  TabStripItemIdentifier* webstate_d_item_identifier =
      CreateTabItemIdentifier(webstate_d);
  EXPECT_EQ(webstate_d->GetUniqueIdentifier(),
            webstate_d_item_identifier.tabSwitcherItem.identifier);
  TabStripItemIdentifier* webstate_e_item_identifier =
      CreateTabItemIdentifier(webstate_e);
  EXPECT_EQ(webstate_e->GetUniqueIdentifier(),
            webstate_e_item_identifier.tabSwitcherItem.identifier);
}

// Test that `CreateGroupItemIdentifier` returns the correct
// `TabStripItemIdentifier` for a TabGroup.
TEST_F(TabStripMediatorUtilsTest, CreateGroupItemIdentifier) {
  WebStateListBuilderFromDescription builder(web_state_list_.get());
  ASSERT_TRUE(builder.BuildWebStateListFromDescription(
      "| [ 0 a b* ] c [ 1 d e ]", base::BindRepeating(CreateWebState)));

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

  TabStripItemIdentifier* group_0_item_identifier =
      CreateGroupItemIdentifier(group_0, web_state_list_.get());
  EXPECT_EQ(group_0, group_0_item_identifier.tabGroupItem.tabGroup);
  TabStripItemIdentifier* group_1_item_identifier =
      CreateGroupItemIdentifier(group_1, web_state_list_.get());
  EXPECT_EQ(group_1, group_1_item_identifier.tabGroupItem.tabGroup);
}

// Test that calling `MoveGroupBeforeTabStripItem` in the same browser works as
// expected.
TEST_F(TabStripMediatorUtilsTest, MoveGroupBeforeItemSameBrowser) {
  WebStateListBuilderFromDescription builder(web_state_list_.get());
  ASSERT_TRUE(builder.BuildWebStateListFromDescription(
      "| [ 0 a b* ] c [ 1 d e ]", base::BindRepeating(CreateWebState)));

  const TabGroup* group_0 = builder.GetTabGroupForIdentifier('0');
  web::WebState* webstate_a = builder.GetWebStateForIdentifier('a');
  web::WebState* webstate_b = builder.GetWebStateForIdentifier('b');
  web::WebState* webstate_c = builder.GetWebStateForIdentifier('c');
  const TabGroup* group_1 = builder.GetTabGroupForIdentifier('1');
  web::WebState* webstate_d = builder.GetWebStateForIdentifier('d');
  web::WebState* webstate_e = builder.GetWebStateForIdentifier('e');

  TabStripItemIdentifier* group_0_item_identifier =
      CreateGroupItemIdentifier(group_0, web_state_list_.get());
  TabStripItemIdentifier* webstate_a_item_identifier =
      CreateTabItemIdentifier(webstate_a);
  TabStripItemIdentifier* webstate_b_item_identifier =
      CreateTabItemIdentifier(webstate_b);
  TabStripItemIdentifier* webstate_c_item_identifier =
      CreateTabItemIdentifier(webstate_c);
  TabStripItemIdentifier* group_1_item_identifier =
      CreateGroupItemIdentifier(group_1, web_state_list_.get());
  TabStripItemIdentifier* webstate_d_item_identifier =
      CreateTabItemIdentifier(webstate_d);
  TabStripItemIdentifier* webstate_e_item_identifier =
      CreateTabItemIdentifier(webstate_e);

  EXPECT_EQ("| [ 0 a b* ] c [ 1 d e ]", builder.GetWebStateListDescription());

  // Move `group_1` before `webstate_c_item_identifier`.
  MoveGroupBeforeTabStripItem(group_1, webstate_c_item_identifier,
                              browser_.get());
  EXPECT_EQ("| [ 0 a b* ] [ 1 d e ] c", builder.GetWebStateListDescription());

  // Move `group_1` at the end of the WebStateList.
  MoveGroupBeforeTabStripItem(group_1, nil, browser_.get());
  EXPECT_EQ("| [ 0 a b* ] c [ 1 d e ]", builder.GetWebStateListDescription());

  // Move `group_1` before `webstate_b_item_identifier`.
  MoveGroupBeforeTabStripItem(group_1, webstate_b_item_identifier,
                              browser_.get());
  EXPECT_EQ("| [ 1 d e ] [ 0 a b* ] c", builder.GetWebStateListDescription());

  // Move `group_0` at the end of the WebStateList.
  MoveGroupBeforeTabStripItem(group_0, nil, browser_.get());
  EXPECT_EQ("| [ 1 d e ] c [ 0 a b* ]", builder.GetWebStateListDescription());

  // Move `group_0` before `group_1_item_identifier`.
  MoveGroupBeforeTabStripItem(group_0, group_1_item_identifier, browser_.get());
  EXPECT_EQ("| [ 0 a b* ] [ 1 d e ] c", builder.GetWebStateListDescription());

  // Move `group_1` before `group_0_item_identifier`.
  MoveGroupBeforeTabStripItem(group_1, group_0_item_identifier, browser_.get());
  EXPECT_EQ("| [ 1 d e ] [ 0 a b* ] c", builder.GetWebStateListDescription());

  // Move `group_0` before `webstate_e_item_identifier`.
  MoveGroupBeforeTabStripItem(group_0, webstate_e_item_identifier,
                              browser_.get());
  EXPECT_EQ("| [ 0 a b* ] [ 1 d e ] c", builder.GetWebStateListDescription());

  // Move `group_1` before `webstate_a_item_identifier`.
  MoveGroupBeforeTabStripItem(group_1, webstate_a_item_identifier,
                              browser_.get());
  EXPECT_EQ("| [ 1 d e ] [ 0 a b* ] c", builder.GetWebStateListDescription());

  // Move `group_1` before `webstate_d_item_identifier`.
  MoveGroupBeforeTabStripItem(group_1, webstate_d_item_identifier,
                              browser_.get());
  EXPECT_EQ("| [ 1 d e ] [ 0 a b* ] c", builder.GetWebStateListDescription());
}

// Test that calling `MoveGroupBeforeTabStripItem` between browsers works as
// expected.
TEST_F(TabStripMediatorUtilsTest, MoveGroupBeforeItemDifferentBrowser) {
  WebStateListBuilderFromDescription builder(web_state_list_.get());
  ASSERT_TRUE(builder.BuildWebStateListFromDescription(
      "| [ 0 a b* ] c", base::BindRepeating(CreateWebState)));
  const TabGroup* group_0 = builder.GetTabGroupForIdentifier('0');
  web::WebState* webstate_a = builder.GetWebStateForIdentifier('a');
  web::WebState* webstate_b = builder.GetWebStateForIdentifier('b');
  web::WebState* webstate_c = builder.GetWebStateForIdentifier('c');
  TabStripItemIdentifier* webstate_c_item_identifier =
      CreateTabItemIdentifier(webstate_c);

  WebStateListBuilderFromDescription other_builder(other_web_state_list_);
  ASSERT_TRUE(other_builder.BuildWebStateListFromDescription(
      "| d [ 1 e f ]", base::BindRepeating(CreateWebState)));
  web::WebState* webstate_d = other_builder.GetWebStateForIdentifier('d');
  const TabGroup* group_1 = other_builder.GetTabGroupForIdentifier('1');
  web::WebState* webstate_e = other_builder.GetWebStateForIdentifier('e');
  web::WebState* webstate_f = other_builder.GetWebStateForIdentifier('f');
  TabStripItemIdentifier* webstate_d_item_identifier =
      CreateTabItemIdentifier(webstate_d);
  TabStripItemIdentifier* webstate_f_item_identifier =
      CreateTabItemIdentifier(webstate_f);

  EXPECT_EQ("| [ 0 a b* ] c", builder.GetWebStateListDescription());
  EXPECT_EQ("| d [ 1 e f ]", other_builder.GetWebStateListDescription());
  EXPECT_CALL(*mock_service_, CreateScopedLocalObserverPauser()).Times(5);
  local_observer_->SetSyncUpdatePaused(true);
  EXPECT_CALL(*mock_service_, RemoveGroup(group_0->tab_group_id())).Times(0);

  // Move `group_0` before `webstate_f_item_identifier` in `other_browser_`.
  MoveGroupBeforeTabStripItem(group_0, webstate_f_item_identifier,
                              other_browser_.get());
  other_builder.SetWebStateIdentifier(webstate_a, 'a');
  other_builder.SetWebStateIdentifier(webstate_b, 'b');
  other_builder.GenerateIdentifiersForWebStateList();
  group_0 = other_builder.GetTabGroupForIdentifier('0');
  EXPECT_EQ("| c*", builder.GetWebStateListDescription());
  EXPECT_EQ("| d [ 0 a b ] [ 1 e f ]",
            other_builder.GetWebStateListDescription());

  // Move `group_0` at the end of the WebStateList in `browser_`.
  MoveGroupBeforeTabStripItem(group_0, nil, browser_.get());
  builder.SetWebStateIdentifier(webstate_a, 'a');
  builder.SetWebStateIdentifier(webstate_b, 'b');
  builder.GenerateIdentifiersForWebStateList();
  group_0 = builder.GetTabGroupForIdentifier('0');
  EXPECT_EQ("| c* [ 0 a b ]", builder.GetWebStateListDescription());
  EXPECT_EQ("| d [ 1 e f ]", other_builder.GetWebStateListDescription());

  // Move `group_1` before `webstate_c_item_identifier` in `browser_`.
  MoveGroupBeforeTabStripItem(group_1, webstate_c_item_identifier,
                              browser_.get());
  builder.SetWebStateIdentifier(webstate_e, 'e');
  builder.SetWebStateIdentifier(webstate_f, 'f');
  builder.GenerateIdentifiersForWebStateList();
  group_1 = builder.GetTabGroupForIdentifier('1');
  EXPECT_EQ("| [ 1 e f ] c* [ 0 a b ]", builder.GetWebStateListDescription());
  EXPECT_EQ("| d", other_builder.GetWebStateListDescription());

  // Move `group_1` before `webstate_d_item_identifier` in `other_browser_`.
  MoveGroupBeforeTabStripItem(group_1, webstate_d_item_identifier,
                              other_browser_.get());
  other_builder.SetWebStateIdentifier(webstate_e, 'e');
  other_builder.SetWebStateIdentifier(webstate_f, 'f');
  group_1 = other_web_state_list_->GetGroupOfWebStateAt(
      other_web_state_list_->GetIndexOfWebState(webstate_e));
  ASSERT_NE(nullptr, group_1);
  other_builder.SetTabGroupIdentifier(group_1, '1');
  EXPECT_EQ("| c* [ 0 a b ]", builder.GetWebStateListDescription());
  EXPECT_EQ("| [ 1 e f ] d", other_builder.GetWebStateListDescription());

  // Move `group_1` before `group_0_item_identifier` in `browser_`.
  MoveGroupBeforeTabStripItem(
      group_1, CreateGroupItemIdentifier(group_0, web_state_list_.get()),
      browser_.get());
  builder.SetWebStateIdentifier(webstate_e, 'e');
  builder.SetWebStateIdentifier(webstate_f, 'f');
  builder.GenerateIdentifiersForWebStateList();
  group_1 = builder.GetTabGroupForIdentifier('1');
  EXPECT_EQ("| c* [ 1 e f ] [ 0 a b ]", builder.GetWebStateListDescription());
  EXPECT_EQ("| d", other_builder.GetWebStateListDescription());
}