chromium/ios/chrome/browser/saved_tab_groups/model/tab_group_local_update_observer_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/tab_group_local_update_observer.h"

#import <memory>
#import <optional>
#import <string>

#import "base/memory/raw_ptr.h"
#import "base/uuid.h"
#import "components/saved_tab_groups/mock_tab_group_sync_service.h"
#import "components/saved_tab_groups/saved_tab_group.h"
#import "components/saved_tab_groups/saved_tab_group_tab.h"
#import "components/saved_tab_groups/types.h"
#import "components/tab_groups/tab_group_color.h"
#import "components/tab_groups/tab_group_id.h"
#import "ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h"
#import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service_factory.h"
#import "ios/chrome/browser/sessions/model/test_session_restoration_service.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/model/web_state_list/web_state_list.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 "ios/web/public/web_state.h"
#import "ios/web/public/web_state_id.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

using testing::_;

using ::testing::AllOf;
using ::testing::Property;
using ::testing::Return;

namespace tab_groups {

namespace {

const char kTestURL[] = "https://chromium.org";

std::unique_ptr<KeyedService> CreateMockSyncService(
    web::BrowserState* context) {
  return std::make_unique<MockTabGroupSyncService>();
}

// Returns the sync tab group prediction for the given `saved_group`.
auto SyncTabGroupPrediction(SavedTabGroup saved_group) {
  return AllOf(
      Property(&SavedTabGroup::local_group_id, saved_group.local_group_id()),
      Property(&SavedTabGroup::title, saved_group.title()),
      Property(&SavedTabGroup::color, saved_group.color()));
}

// Returns a test `SavedTabGroup`.
SavedTabGroup TestSavedGroup() {
  SavedTabGroup saved_group(u"Test title", tab_groups::TabGroupColorId::kBlue,
                            {}, std::nullopt, base::Uuid::GenerateRandomV4(),
                            TabGroupId::GenerateNew());
  return saved_group;
}

MATCHER_P(TabTitleEq, title, "") {
  return arg.title() == title;
}

MATCHER_P(TabURLEq, url, "") {
  return arg.url() == url;
}

}  // namespace

class TabGroupLocalUpdateObserverTest : public PlatformTest {
 public:
  TabGroupLocalUpdateObserverTest() {
    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(TabGroupSyncServiceFactory::GetInstance(),
                              base::BindRepeating(&CreateMockSyncService));
    builder.AddTestingFactory(
        SessionRestorationServiceFactory::GetInstance(),
        TestSessionRestorationService::GetTestingFactory());
    browser_state_ = std::move(builder).Build();

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

    browser_ = std::make_unique<TestBrowser>(
        browser_state_.get(), nil,
        std::make_unique<FakeWebStateListDelegate>());
    browser_same_browser_state_ = std::make_unique<TestBrowser>(
        browser_state_.get(), nil,
        std::make_unique<FakeWebStateListDelegate>());

    other_browser_state_ = TestChromeBrowserState::Builder().Build();
    other_browser_ = std::make_unique<TestBrowser>(
        other_browser_state_.get(), nil,
        std::make_unique<FakeWebStateListDelegate>());

    browser_list_ =
        BrowserListFactory::GetForBrowserState(browser_state_.get());
    local_observer_ = std::make_unique<TabGroupLocalUpdateObserver>(
        browser_list_.get(), mock_service_);
    browser_list_->AddBrowser(browser_.get());

    BrowserList* other_browser_list =
        BrowserListFactory::GetForBrowserState(other_browser_state_.get());
    other_browser_list->AddBrowser(other_browser_.get());
  }

  // Returns a new fake web state.
  std::unique_ptr<web::FakeWebState> CreateWebState() {
    std::unique_ptr<web::FakeWebState> web_state =
        std::make_unique<web::FakeWebState>(web::WebStateID::NewUnique());
    web_state->SetCurrentURL(GURL("http://first-url.com"));
    web_state->SetTitle(u"original title");
    return web_state;
  }

  // Inserts a new FakeWebState in `web_state_list`.
  web::FakeWebState* InsertWebState(WebStateList* web_state_list) {
    std::unique_ptr<web::FakeWebState> unique_web_state = CreateWebState();
    web::FakeWebState* web_state = unique_web_state.get();
    web_state_list->InsertWebState(std::move(unique_web_state));
    return web_state;
  }

  // Creates a vector of `saved_tabs` based on the given `range`.
  std::vector<SavedTabGroupTab> SavedTabGroupTabsFromTabs(
      std::vector<int> indexes,
      WebStateList* web_state_list,
      base::Uuid saved_tab_group_id) {
    std::vector<SavedTabGroupTab> saved_tabs;
    for (int index : indexes) {
      web::WebState* web_state = web_state_list->GetWebStateAt(index);
      SavedTabGroupTab saved_tab(web_state->GetVisibleURL(),
                                 web_state->GetTitle(), saved_tab_group_id,
                                 std::make_optional(index), std::nullopt,
                                 web_state->GetUniqueIdentifier().identifier());
      saved_tabs.push_back(saved_tab);
    }
    return saved_tabs;
  }

  void SetUpNavigationContext(web::FakeWebState* web_state) {
    navigation_item_ = web::NavigationItem::Create();
    navigation_item_->SetTransitionType(ui::PAGE_TRANSITION_TYPED);

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

    web_state->SetNavigationManager(std::move(navigation_manager));
    web_state->SetBrowserState(browser_state_.get());

    navigation_context_ = std::make_unique<web::FakeNavigationContext>();
    navigation_context_->SetWebState(web_state);

    navigation_context_->SetUrl(GURL(kTestURL));
    navigation_context_->SetHasCommitted(true);
    navigation_context_->SetPageTransition(ui::PAGE_TRANSITION_LINK);
    navigation_context_->SetHasUserGesture(true);
    navigation_context_->SetIsRendererInitiated(false);
    navigation_context_->SetIsPost(false);
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<TestBrowser> browser_;
  std::unique_ptr<TestBrowser> browser_same_browser_state_;
  std::unique_ptr<TestChromeBrowserState> other_browser_state_;
  std::unique_ptr<TestBrowser> other_browser_;
  raw_ptr<BrowserList> browser_list_;
  std::unique_ptr<TabGroupLocalUpdateObserver> local_observer_;
  raw_ptr<MockTabGroupSyncService> mock_service_;
  const std::u16string kNewTitle = u"title to update";
  std::unique_ptr<web::NavigationItem> navigation_item_;
  std::unique_ptr<web::FakeNavigationContext> navigation_context_;
};

// Tests that the service is correctly updated when the title of a tab that was
// added after creating the service is updated.
TEST_F(TabGroupLocalUpdateObserverTest, TitleUpdateExistingTab) {
  WebStateList* web_state_list = browser_->GetWebStateList();

  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);

  EXPECT_CALL(*mock_service_, UpdateTab(tab_group_id, web_state_id.identifier(),
                                        TabTitleEq(kNewTitle)))
      .Times(1);
  web_state->SetTitle(kNewTitle);
}

// Tests that the service is correctly updated when the title of a tab that was
// existing when creating the service is updated.
TEST_F(TabGroupLocalUpdateObserverTest, TitleUpdateNewTab) {
  WebStateList* web_state_list = browser_->GetWebStateList();

  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);

  EXPECT_CALL(*mock_service_, UpdateTab(tab_group_id, web_state_id.identifier(),
                                        TabTitleEq(kNewTitle)));
  web_state->SetTitle(kNewTitle);
}

// Tests that the service is does not update the title when sync is paused.
TEST_F(TabGroupLocalUpdateObserverTest, TitleUpdateNewTabSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_->GetWebStateList();

  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);

  EXPECT_CALL(*mock_service_, UpdateTab(tab_group_id, web_state_id.identifier(),
                                        TabTitleEq(kNewTitle)))
      .Times(0);
  web_state->SetTitle(kNewTitle);
}

// Tests that the service is correctly updated when the title of a tab that is
// in a WebStateList that was added after the service creation is updated.
TEST_F(TabGroupLocalUpdateObserverTest, TitleUpdateNewWebStateList) {
  WebStateList* web_state_list = browser_same_browser_state_->GetWebStateList();

  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);

  // Add the Browser after the tab is inserted.
  BrowserListFactory::GetForBrowserState(browser_state_.get())
      ->AddBrowser(browser_same_browser_state_.get());

  EXPECT_CALL(*mock_service_, UpdateTab(tab_group_id, web_state_id.identifier(),
                                        TabTitleEq(kNewTitle)));
  web_state->SetTitle(kNewTitle);
}

// Tests that the service is correctly updated when the title of a tab that
// inserted in a WebStateList that was added after the service creation is
// updated.
TEST_F(TabGroupLocalUpdateObserverTest, TitleUpdateNewWebStateListInsert) {
  // Add the browser before inserting the tab.
  BrowserListFactory::GetForBrowserState(browser_state_.get())
      ->AddBrowser(browser_same_browser_state_.get());

  WebStateList* web_state_list = browser_same_browser_state_->GetWebStateList();

  std::unique_ptr<web::FakeWebState> unique_web_state = CreateWebState();
  web::WebStateID web_state_id = unique_web_state->GetUniqueIdentifier();
  web::FakeWebState* web_state = unique_web_state.get();
  web_state_list->InsertWebState(std::move(unique_web_state));

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);

  EXPECT_CALL(*mock_service_, UpdateTab(tab_group_id, web_state_id.identifier(),
                                        TabTitleEq(kNewTitle)));
  web_state->SetTitle(kNewTitle);
}

// Tests that the service is correctly updated when the navigation of a tab is
// updated.
TEST_F(TabGroupLocalUpdateObserverTest, NavigationUpdate) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);

  EXPECT_CALL(*mock_service_, UpdateTab(tab_group_id, web_state_id.identifier(),
                                        TabURLEq(GURL(kTestURL))));
  web_state->SetCurrentURL(GURL(kTestURL));
  SetUpNavigationContext(web_state);
  web_state->OnNavigationFinished(navigation_context_.get());
}

// Tests that the service is not updated when the new active tab is not in the
// group.
TEST_F(TabGroupLocalUpdateObserverTest, ActivateRegularTab) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  InsertWebState(web_state_list);
  InsertWebState(web_state_list);

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);
  web_state_list->ActivateWebStateAt(0);

  EXPECT_CALL(*mock_service_, UpdateTab(_, _, _)).Times(0);
  EXPECT_CALL(*mock_service_, AddTab(_, _, _, _, _)).Times(0);
  EXPECT_CALL(*mock_service_, RemoveTab(_, _)).Times(0);
  EXPECT_CALL(*mock_service_, MoveTab(_, _, _)).Times(0);

  web_state_list->ActivateWebStateAt(1);
}

// Tests that the service is not updated when sync is paused and the navigation
// of a tab is updated.
TEST_F(TabGroupLocalUpdateObserverTest, NavigationUpdateSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_list->CreateGroup({0}, {}, tab_group_id);

  EXPECT_CALL(*mock_service_, UpdateTab(tab_group_id, web_state_id.identifier(),
                                        TabURLEq(GURL(kTestURL))))
      .Times(0);
  web_state->SetCurrentURL(GURL(kTestURL));
  SetUpNavigationContext(web_state);
  web_state->OnNavigationFinished(navigation_context_.get());
}

// Tests that the service is correctly updated when a tab is added to a newly
// created group.
TEST_F(TabGroupLocalUpdateObserverTest, AddTabToNewGroup) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web_state->SetCurrentURL(GURL(kTestURL));
  web_state->SetTitle(kNewTitle);

  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = TabGroupId::GenerateNew();

  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_id, web_state_id.identifier(), kNewTitle,
                     GURL(kTestURL), std::make_optional(0ul)));
  web_state_list->CreateGroup({0}, {}, tab_group_id);
}

// Tests that the service is not updated when sync is paused and a tab is added
// to a group.
TEST_F(TabGroupLocalUpdateObserverTest, AddTabSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state = InsertWebState(web_state_list);
  web_state->SetCurrentURL(GURL(kTestURL));
  web_state->SetTitle(kNewTitle);

  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = TabGroupId::GenerateNew();

  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_id, web_state_id.identifier(), kNewTitle,
                     GURL(kTestURL), std::make_optional(0ul)))
      .Times(0);
  web_state_list->CreateGroup({0}, {}, tab_group_id);
}

// Tests that the service is correctly updated when tabs are moved in and out of
// a group.
TEST_F(TabGroupLocalUpdateObserverTest, MoveTabToGroup) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state_1 = InsertWebState(web_state_list);
  web::FakeWebState* web_state_2 = InsertWebState(web_state_list);
  web::FakeWebState* web_state_3 = InsertWebState(web_state_list);

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  web_state_3->SetCurrentURL(GURL(kTestURL));
  web_state_3->SetTitle(kNewTitle);
  web::WebStateID web_state_1_id = web_state_1->GetUniqueIdentifier();
  web::WebStateID web_state_2_id = web_state_2->GetUniqueIdentifier();
  web::WebStateID web_state_3_id = web_state_3->GetUniqueIdentifier();

  // Create the group and insert `web_state_1`.
  EXPECT_CALL(*mock_service_, AddTab(tab_group_id, web_state_1_id.identifier(),
                                     _, _, std::make_optional(0ul)));
  const TabGroup* group = web_state_list->CreateGroup({0}, {}, tab_group_id);
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
      .WillOnce(Return(TestSavedGroup()));

  // Move `web_state_2` in the group.
  EXPECT_CALL(*mock_service_, AddTab(tab_group_id, web_state_2_id.identifier(),
                                     _, _, std::make_optional(1ul)));
  web_state_list->MoveToGroup({1}, group);

  // Move `web_state_3` in the group.
  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_id, web_state_3_id.identifier(), kNewTitle,
                     GURL(kTestURL), std::make_optional(2ul)));
  web_state_list->MoveToGroup({2}, group);

  // Move `web_state_3` at the beginning of the group.
  EXPECT_CALL(*mock_service_,
              MoveTab(tab_group_id, web_state_3_id.identifier(), 0));
  web_state_list->MoveWebStateAt(2, 0);

  // Move `web_state_3` out of the group.
  EXPECT_CALL(*mock_service_,
              RemoveTab(tab_group_id, web_state_3_id.identifier()));
  web_state_list->RemoveFromGroups({0});
}

// Tests that the service is correctly updated when tabs are moved between
// groups.
TEST_F(TabGroupLocalUpdateObserverTest, MoveTabFromOtherGroup) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state_1 = InsertWebState(web_state_list);
  web::FakeWebState* web_state_2 = InsertWebState(web_state_list);
  web::FakeWebState* web_state_3 = InsertWebState(web_state_list);

  TabGroupId tab_group_1_id = TabGroupId::GenerateNew();
  TabGroupId tab_group_2_id = TabGroupId::GenerateNew();

  web::WebStateID web_state_1_id = web_state_1->GetUniqueIdentifier();
  web::WebStateID web_state_2_id = web_state_2->GetUniqueIdentifier();
  web::WebStateID web_state_3_id = web_state_3->GetUniqueIdentifier();

  // Create a first group and insert `web_state_1`.
  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_1_id, web_state_1_id.identifier(), _, _,
                     std::make_optional(0ul)));
  const TabGroup* group_1 =
      web_state_list->CreateGroup({0}, {}, tab_group_1_id);

  // Create a second group and insert `web_state_2`.
  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_2_id, web_state_2_id.identifier(), _, _,
                     std::make_optional(0ul)));
  const TabGroup* group_2 =
      web_state_list->CreateGroup({1}, {}, tab_group_2_id);
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_2_id))
      .WillRepeatedly(Return(TestSavedGroup()));

  // Move `web_state_3` in the second group.
  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_2_id, web_state_3_id.identifier(), _, _,
                     std::make_optional(1ul)));
  web_state_list->MoveToGroup({2}, group_2);

  // Move `web_state_2` in the first group.
  EXPECT_CALL(*mock_service_,
              RemoveTab(tab_group_2_id, web_state_2_id.identifier()));
  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_1_id, web_state_2_id.identifier(), _, _,
                     std::make_optional(1ul)));
  web_state_list->MoveToGroup({1}, group_1);
}

// Tests that the service is correctly updated when a tab is removed from a
// group.
TEST_F(TabGroupLocalUpdateObserverTest, RemoveFromGroup) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state = InsertWebState(web_state_list);
  InsertWebState(web_state_list);

  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = TabGroupId::GenerateNew();

  web_state_list->CreateGroup({0, 1}, {}, tab_group_id);
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
      .WillOnce(Return(TestSavedGroup()));

  EXPECT_CALL(*mock_service_,
              RemoveTab(tab_group_id, web_state_id.identifier()));
  web_state_list->CloseWebStateAt(/*index*/ 0, WebStateList::CLOSE_NO_FLAGS);
}

// Tests that the service is not updated when sync is pausded and a tab is
// removed from a group.
TEST_F(TabGroupLocalUpdateObserverTest, RemoveFromGroupSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state = InsertWebState(web_state_list);
  InsertWebState(web_state_list);

  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = TabGroupId::GenerateNew();

  web_state_list->CreateGroup({0, 1}, {}, tab_group_id);
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id)).Times(0);

  EXPECT_CALL(*mock_service_,
              RemoveTab(tab_group_id, web_state_id.identifier()))
      .Times(0);
  web_state_list->CloseWebStateAt(/*index*/ 0, WebStateList::CLOSE_NO_FLAGS);
}

// Tests that the service is correctly updated when a raw tab group is created.
TEST_F(TabGroupLocalUpdateObserverTest, CreateRawSyncedGroup) {
  WebStateList* web_state_list = browser_same_browser_state_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| a b c* d e f"));

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  base::Uuid saved_tab_group_id = base::Uuid::GenerateRandomV4();

  std::vector<SavedTabGroupTab> saved_tabs =
      SavedTabGroupTabsFromTabs({0}, web_state_list, saved_tab_group_id);
  SavedTabGroup saved_group(
      u"", TabGroup::DefaultColorForNewTabGroup(web_state_list), saved_tabs,
      std::nullopt, saved_tab_group_id, tab_group_id);

  BrowserListFactory::GetForBrowserState(browser_state_.get())
      ->AddBrowser(browser_same_browser_state_.get());

  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id));
  EXPECT_CALL(*mock_service_, AddGroup(SyncTabGroupPrediction(saved_group)));
  web_state_list->CreateGroup({0}, {}, tab_group_id);
}

// Tests that the service is correctly updated when a tab group is created.
TEST_F(TabGroupLocalUpdateObserverTest, CreateNamedSyncedGroup) {
  WebStateList* web_state_list = browser_same_browser_state_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| [0 a b] c* d e f"));

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  base::Uuid saved_tab_group_id = base::Uuid::GenerateRandomV4();

  std::vector<SavedTabGroupTab> saved_tabs =
      SavedTabGroupTabsFromTabs({2, 3, 4}, web_state_list, saved_tab_group_id);
  SavedTabGroup saved_group(u"Test title", tab_groups::TabGroupColorId::kBlue,
                            saved_tabs, std::nullopt, saved_tab_group_id,
                            tab_group_id);

  BrowserListFactory::GetForBrowserState(browser_state_.get())
      ->AddBrowser(browser_same_browser_state_.get());

  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id));
  EXPECT_CALL(*mock_service_, AddGroup(SyncTabGroupPrediction(saved_group)));
  tab_groups::TabGroupVisualData visual_data(
      u"Test title", tab_groups::TabGroupColorId::kBlue);
  web_state_list->CreateGroup({2, 3, 4}, visual_data, tab_group_id);
}

// Tests that the service is not updated when sync is paused.
TEST_F(TabGroupLocalUpdateObserverTest, CreateSyncedGroupSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_same_browser_state_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| a b c* d e f"));

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  base::Uuid saved_tab_group_id = base::Uuid::GenerateRandomV4();

  std::vector<SavedTabGroupTab> saved_tabs =
      SavedTabGroupTabsFromTabs({0}, web_state_list, saved_tab_group_id);
  SavedTabGroup saved_group(
      u"", TabGroup::DefaultColorForNewTabGroup(web_state_list), saved_tabs,
      std::nullopt, saved_tab_group_id, tab_group_id);

  BrowserListFactory::GetForBrowserState(browser_state_.get())
      ->AddBrowser(browser_same_browser_state_.get());

  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id));
  EXPECT_CALL(*mock_service_, AddGroup(SyncTabGroupPrediction(saved_group)))
      .Times(0);
  web_state_list->CreateGroup({0}, {}, tab_group_id);
}

// Tests that the service is correctly updated when the visual data of a group
// is updated.
TEST_F(TabGroupLocalUpdateObserverTest, UpdateVisualData) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  InsertWebState(web_state_list);
  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  const TabGroup* group = web_state_list->CreateGroup({0}, {}, tab_group_id);
  TabGroupVisualData visual_data(u"Updated Group Name",
                                 tab_groups::TabGroupColorId::kRed);

  EXPECT_CALL(*mock_service_,
              UpdateVisualData(tab_group_id, &group->visual_data()));
  web_state_list->UpdateGroupVisualData(group, visual_data);
}

// Tests that the service is correctly updated when the visual data of a group
// is updated.
TEST_F(TabGroupLocalUpdateObserverTest, UpdateVisualDataSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_->GetWebStateList();
  InsertWebState(web_state_list);
  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  const TabGroup* group = web_state_list->CreateGroup({0}, {}, tab_group_id);
  TabGroupVisualData visual_data(u"Updated Group Name",
                                 tab_groups::TabGroupColorId::kRed);

  EXPECT_CALL(*mock_service_,
              UpdateVisualData(tab_group_id, &group->visual_data()))
      .Times(0);
  web_state_list->UpdateGroupVisualData(group, visual_data);
}

// Tests that the service is correctly updated when a tab is replaced in a
// group.
TEST_F(TabGroupLocalUpdateObserverTest, ReplaceTabInGroup) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| [0 a b] c* d e f"));

  web::WebState* web_state_b = builder.GetWebStateForIdentifier('b');
  const TabGroup* group = builder.GetTabGroupForIdentifier('0');
  auto passed_web_state = std::make_unique<web::FakeWebState>();

  web::WebStateID web_state_b_id = web_state_b->GetUniqueIdentifier();
  web::WebStateID passed_web_state_id = passed_web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = group->tab_group_id();
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
      .WillOnce(Return(TestSavedGroup()));

  EXPECT_CALL(*mock_service_,
              RemoveTab(tab_group_id, web_state_b_id.identifier()));
  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_id, passed_web_state_id.identifier(), _, _,
                     std::make_optional(1ul)));
  web_state_list->ReplaceWebStateAt(/*index=*/1, std::move(passed_web_state));
}

// Tests that the service is not updated when sync is paused and a tab is
// replaced in a group.
TEST_F(TabGroupLocalUpdateObserverTest, ReplaceTabInGroupSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| [0 a b] c* d e f"));

  web::WebState* web_state_b = builder.GetWebStateForIdentifier('b');
  const TabGroup* group = builder.GetTabGroupForIdentifier('0');
  auto passed_web_state = std::make_unique<web::FakeWebState>();

  web::WebStateID web_state_b_id = web_state_b->GetUniqueIdentifier();
  web::WebStateID passed_web_state_id = passed_web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = group->tab_group_id();
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id)).Times(0);

  EXPECT_CALL(*mock_service_,
              RemoveTab(tab_group_id, web_state_b_id.identifier()))
      .Times(0);
  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_id, passed_web_state_id.identifier(), _, _,
                     std::make_optional(1ul)))
      .Times(0);
  web_state_list->ReplaceWebStateAt(/*index=*/1, std::move(passed_web_state));
}

// Tests that the service is correctly updated when a tab is inserted in a
// group.
TEST_F(TabGroupLocalUpdateObserverTest, InsertInGroup) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| [0 a b] c* d e f"));

  const TabGroup* group = builder.GetTabGroupForIdentifier('0');
  auto passed_web_state = std::make_unique<web::FakeWebState>();

  web::WebStateID passed_web_state_id = passed_web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = group->tab_group_id();

  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_id, passed_web_state_id.identifier(), _, _,
                     std::make_optional(1ul)));
  web_state_list->InsertWebState(std::move(passed_web_state),
                                 WebStateList::InsertionParams::AtIndex(1));
}

// Tests that the service is not updated when sync is pauded and a tab is
// inserted in a group.
TEST_F(TabGroupLocalUpdateObserverTest, InsertInGroupSyncPaused) {
  local_observer_->SetSyncUpdatePaused(true);

  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| [0 a b] c* d e f"));

  const TabGroup* group = builder.GetTabGroupForIdentifier('0');
  auto passed_web_state = std::make_unique<web::FakeWebState>();

  web::WebStateID passed_web_state_id = passed_web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = group->tab_group_id();

  EXPECT_CALL(*mock_service_,
              AddTab(tab_group_id, passed_web_state_id.identifier(), _, _,
                     std::make_optional(1ul)))
      .Times(0);
  web_state_list->InsertWebState(std::move(passed_web_state),
                                 WebStateList::InsertionParams::AtIndex(1));
}

// Tests that the service is correctly updated when a tab group is deleted.
TEST_F(TabGroupLocalUpdateObserverTest, DeleteGroup) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  InsertWebState(web_state_list);
  InsertWebState(web_state_list);

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  const TabGroup* group = web_state_list->CreateGroup({0, 1}, {}, tab_group_id);
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
      .WillRepeatedly(Return(TestSavedGroup()));

  EXPECT_CALL(*mock_service_, RemoveLocalTabGroupMapping(tab_group_id))
      .Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id));
  web_state_list->DeleteGroup(group);
}

// Tests that the service is correctly updated when a tab group is closed
// locally.
TEST_F(TabGroupLocalUpdateObserverTest, CloseGroupLocally) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  InsertWebState(web_state_list);
  InsertWebState(web_state_list);
  InsertWebState(web_state_list);
  InsertWebState(web_state_list);

  TabGroupId tab_group_id = TabGroupId::GenerateNew();
  const TabGroup* group = web_state_list->CreateGroup({0, 1}, {}, tab_group_id);
  EXPECT_EQ(1u, web_state_list->GetGroups().size());
  std::optional<SavedTabGroup> saved_group = TestSavedGroup();

  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
      .WillRepeatedly([&saved_group] { return saved_group; });
  EXPECT_CALL(*mock_service_, RemoveLocalTabGroupMapping(tab_group_id))
      .WillOnce([&saved_group] { saved_group = std::nullopt; });
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id)).Times(0);
  EXPECT_CALL(*mock_service_, RemoveTab(tab_group_id, _)).Times(0);
  utils::CloseTabGroupLocally(group, web_state_list, mock_service_);

  EXPECT_EQ(0u, web_state_list->GetGroups().size());
  EXPECT_EQ(2, web_state_list->count());

  web_state_list->MoveWebStateAt(0, 1);
  web_state_list->CloseWebStateAt(/*index*/ 0, WebStateList::CLOSE_NO_FLAGS);
  EXPECT_EQ(1, web_state_list->count());
}

// Tests that the service is correctly updated when the last tab of a group is
// deleted.
TEST_F(TabGroupLocalUpdateObserverTest, DeleteGroupAfterRemovingLastTtab) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  web::FakeWebState* web_state = InsertWebState(web_state_list);
  InsertWebState(web_state_list);

  web::WebStateID web_state_id = web_state->GetUniqueIdentifier();
  TabGroupId tab_group_id = TabGroupId::GenerateNew();

  web_state_list->CreateGroup({0}, {}, tab_group_id);
  EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
      .WillRepeatedly(Return(TestSavedGroup()));

  EXPECT_CALL(*mock_service_,
              RemoveTab(tab_group_id, web_state_id.identifier()));
  EXPECT_CALL(*mock_service_, RemoveLocalTabGroupMapping(tab_group_id))
      .Times(0);
  EXPECT_CALL(*mock_service_, RemoveGroup(tab_group_id));
  web_state_list->CloseWebStateAt(/*index*/ 0, WebStateList::CLOSE_NO_FLAGS);
}

// Tests that the service is correctly called when the active tab is updated.
TEST_F(TabGroupLocalUpdateObserverTest, UpdateActiveTab) {
  WebStateList* web_state_list = browser_->GetWebStateList();
  WebStateListBuilderFromDescription builder(web_state_list);
  ASSERT_TRUE(builder.BuildWebStateListFromDescription("| [0 a b] c* d e f"));

  const TabGroup* group = builder.GetTabGroupForIdentifier('0');
  web::WebState* web_state_a = builder.GetWebStateForIdentifier('a');

  EXPECT_CALL(*mock_service_,
              OnTabSelected(group->tab_group_id(),
                            web_state_a->GetUniqueIdentifier().identifier()));
  web_state_list->ActivateWebStateAt(0);
}

}  // namespace tab_groups