910e62b5创建于 1月15日历史提交
// 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.

#include "components/tabs/public/tab_collection_storage.h"

#include <memory>

#include "base/strings/string_number_conversions.h"
#include "base/test/gtest_util.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/tab_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/test/base/testing_profile.h"
#include "components/tabs/public/pinned_tab_collection.h"
#include "components/tabs/public/tab_collection.h"
#include "components/tabs/public/tab_group_tab_collection.h"
#include "components/tabs/public/unpinned_tab_collection.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h"

class TabCollectionStorageTest : public ::testing::Test {
 public:
  TabCollectionStorageTest() {
    pinned_collection_ = std::make_unique<tabs::PinnedTabCollection>();
    testing_profile_ = std::make_unique<TestingProfile>();
    tab_strip_model_delegate_ = std::make_unique<TestTabStripModelDelegate>();
    tab_strip_model_ = std::make_unique<TabStripModel>(
        tab_strip_model_delegate_.get(), testing_profile_.get());
  }
  TabCollectionStorageTest(const TabCollectionStorageTest&) = delete;
  TabCollectionStorageTest& operator=(const TabCollectionStorageTest&) = delete;
  ~TabCollectionStorageTest() override { pinned_collection_.reset(); }

  tabs::TabCollectionStorage* GetTabCollectionStorage() {
    return pinned_collection_->GetTabCollectionStorageForTesting();
  }

  TabStripModel* GetTabStripModel() { return tab_strip_model_.get(); }

  void AddTabs(int num) {
    for (int i = 0; i < num; i++) {
      std::unique_ptr<tabs::TabModel> tab_model =
          std::make_unique<tabs::TabModel>(MakeWebContents(),
                                           GetTabStripModel());
      tabs::TabModel* tab_model_ptr = tab_model.get();

      tabs::TabInterface* inserted_tab_model_ptr =
          GetTabCollectionStorage()->AddTab(
              std::move(tab_model),
              GetTabCollectionStorage()->GetChildrenCount());
      EXPECT_EQ(tab_model_ptr, inserted_tab_model_ptr);
      EXPECT_EQ(GetTabCollectionStorage()->GetIndexOfTab(tab_model_ptr),
                GetTabCollectionStorage()->GetChildrenCount() - 1);
    }
  }

  void SetTabID(tabs::TabInterface* tab_model, int id) {
    std::string identifier =
        "T" + base::NumberToString(reinterpret_cast<uintptr_t>(tab_model));
    storage_children_to_id_map_[identifier] = "T" + base::NumberToString(id);
  }

  void SetCollectionID(tabs::TabCollection* collection, int id) {
    std::string identifier =
        "C" + base::NumberToString(reinterpret_cast<uintptr_t>(collection));
    storage_children_to_id_map_[identifier] = "C" + base::NumberToString(id);
  }

  void ResetStorageChildrenIDs(int start) {
    int tab_i = 0;
    int collection_i = 0;
    const auto& children = GetTabCollectionStorage()->GetChildren();
    for (const auto& child : children) {
      if (std::holds_alternative<std::unique_ptr<tabs::TabInterface>>(child)) {
        SetTabID(std::get<std::unique_ptr<tabs::TabInterface>>(child).get(),
                 start + tab_i);
        tab_i += 1;
      } else if (std::holds_alternative<std::unique_ptr<tabs::TabCollection>>(
                     child)) {
        SetCollectionID(
            std::get<std::unique_ptr<tabs::TabCollection>>(child).get(),
            start + collection_i);
        collection_i += 1;
      }
    }
  }

  std::vector<std::string> StorageCollectionChildrenString() {
    std::vector<std::string> ids;
    const auto& children = GetTabCollectionStorage()->GetChildren();
    for (const auto& child : children) {
      std::string identifier;
      if (std::holds_alternative<std::unique_ptr<tabs::TabInterface>>(child)) {
        tabs::TabInterface* tab =
            std::get<std::unique_ptr<tabs::TabInterface>>(child).get();
        identifier =
            "T" + base::NumberToString(reinterpret_cast<uintptr_t>(tab));
      } else if (std::holds_alternative<std::unique_ptr<tabs::TabCollection>>(
                     child)) {
        tabs::TabCollection* collection =
            std::get<std::unique_ptr<tabs::TabCollection>>(child).get();
        identifier =
            "C" + base::NumberToString(reinterpret_cast<uintptr_t>(collection));
      }
      ids.push_back(storage_children_to_id_map_[identifier]);
    }
    return ids;
  }

  std::unique_ptr<content::WebContents> MakeWebContents() {
    return content::WebContents::Create(
        content::WebContents::CreateParams(testing_profile_.get()));
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  content::RenderViewHostTestEnabler test_enabler_;
  std::unique_ptr<Profile> testing_profile_;
  std::unique_ptr<tabs::PinnedTabCollection> pinned_collection_;
  std::unique_ptr<TestTabStripModelDelegate> tab_strip_model_delegate_;
  std::unique_ptr<TabStripModel> tab_strip_model_;
  std::map<std::string, std::string> storage_children_to_id_map_;
  const tabs::TabModel::PreventFeatureInitializationForTesting prevent_;
};

TEST_F(TabCollectionStorageTest, AddTabOperation) {
  auto tab_model_one =
      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
  auto tab_model_two =
      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());

  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
  tabs::TabModel* tab_model_two_ptr = tab_model_two.get();

  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
  collection_storage->AddTab(std::move(tab_model_one), 0);

  EXPECT_TRUE(collection_storage->ContainsTab(tab_model_one_ptr));
  EXPECT_FALSE(collection_storage->ContainsTab(tab_model_two.get()));

  // Add four more tabs.
  AddTabs(4);
  ResetStorageChildrenIDs(0);

  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);

  // Annotate `tab_model_two_ptr` with an id of 5.
  SetTabID(tab_model_two_ptr, 5);
  collection_storage->AddTab(std::move(tab_model_two), 3ul);
  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_two_ptr), 3ul);
  EXPECT_EQ(collection_storage->GetTabAtIndex(3ul), tab_model_two_ptr);
  EXPECT_EQ(StorageCollectionChildrenString(),
            (std::vector<std::string>{"T0", "T1", "T2", "T5", "T3", "T4"}));

  // TODO(b/332586827):Add death testing for out of bounds index.
}

TEST_F(TabCollectionStorageTest, RemoveTabOperation) {
  auto tab_model_one =
      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();

  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();

  // Add four tabs
  AddTabs(4);

  // Add `tab_model_one` to index 3.
  collection_storage->AddTab(std::move(tab_model_one), 3ul);
  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
  ResetStorageChildrenIDs(0);

  auto removed_tab = collection_storage->RemoveTab(tab_model_one_ptr);

  EXPECT_EQ(collection_storage->GetChildrenCount(), 4ul);
  EXPECT_EQ(removed_tab.get(), tab_model_one_ptr);
  // `tab_model_one_ptr` was removed from index 3.
  EXPECT_EQ(StorageCollectionChildrenString(),
            (std::vector<std::string>{"T0", "T1", "T2", "T4"}));
}

TEST_F(TabCollectionStorageTest, MoveTabOperation) {
  auto tab_model_one =
      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();

  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();

  // Add four tabs
  AddTabs(4);

  // Add `tab_model_one` to index 3.
  collection_storage->AddTab(std::move(tab_model_one), 3ul);
  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_one_ptr), 3ul);
  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
  ResetStorageChildrenIDs(0);

  collection_storage->MoveTab(tab_model_one_ptr, 1ul);

  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_one_ptr), 1ul);
  EXPECT_EQ(StorageCollectionChildrenString(),
            (std::vector<std::string>{"T0", "T3", "T1", "T2", "T4"}));

  collection_storage->MoveTab(tab_model_one_ptr, 4ul);
  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_one_ptr), 4ul);
  EXPECT_EQ(StorageCollectionChildrenString(),
            (std::vector<std::string>{"T0", "T1", "T2", "T4", "T3"}));
}

// TODO(b/332586827): Re-enable death testing.
TEST_F(TabCollectionStorageTest, DISABLED_InvalidArgumentsTabOperations) {
  auto tab_model_one =
      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
  std::unique_ptr<tabs::TabModel> empty_ptr;

  EXPECT_DEATH_IF_SUPPORTED(
      collection_storage->AddTab(std::make_unique<tabs::TabModel>(
                                     MakeWebContents(), GetTabStripModel()),
                                 10ul),
      "");
  EXPECT_DEATH_IF_SUPPORTED(
      collection_storage->AddTab(std::move(empty_ptr), 1ul), "");

  EXPECT_DEATH_IF_SUPPORTED(
      {
        std::unique_ptr<tabs::TabInterface> tab =
            collection_storage->RemoveTab(tab_model_one.get());
      },
      "");
  EXPECT_DEATH_IF_SUPPORTED(
      {
        std::unique_ptr<tabs::TabInterface> tab =
            collection_storage->RemoveTab(nullptr);
      },
      "");

  EXPECT_DEATH_IF_SUPPORTED(
      collection_storage->MoveTab(tab_model_one.get(), 0ul), "");
  collection_storage->AddTab(std::move(tab_model_one), 0ul);
  EXPECT_DEATH_IF_SUPPORTED(
      collection_storage->MoveTab(tab_model_one.get(), 10ul), "");
  EXPECT_DEATH_IF_SUPPORTED(collection_storage->MoveTab(nullptr, 10ul), "");
}

TEST_F(TabCollectionStorageTest, AddMixedTabAndCollectionOperation) {
  auto tab_collection_one = std::make_unique<tabs::UnpinnedTabCollection>();
  auto tab_collection_two = std::make_unique<tabs::UnpinnedTabCollection>();

  tabs::TabCollection* tab_collection_one_ptr = tab_collection_one.get();
  tabs::TabCollection* tab_collection_two_ptr = tab_collection_two.get();

  // This is the top level collection storage.
  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();

  collection_storage->AddCollection(std::move(tab_collection_one), 0);

  EXPECT_TRUE(collection_storage->ContainsCollection(tab_collection_one_ptr));
  EXPECT_FALSE(collection_storage->ContainsCollection(tab_collection_two_ptr));

  // Add four more tabs.
  AddTabs(4);
  ResetStorageChildrenIDs(0);

  // Set collection ids of both the sub collections.
  SetCollectionID(tab_collection_one_ptr, 0);
  SetCollectionID(tab_collection_two_ptr, 1);

  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);

  collection_storage->AddCollection(std::move(tab_collection_two), 3ul);
  EXPECT_EQ(collection_storage->GetIndexOfCollection(tab_collection_two_ptr),
            3ul);
  EXPECT_EQ(collection_storage->GetIndexOfCollection(tab_collection_one_ptr),
            0ul);
  EXPECT_EQ(StorageCollectionChildrenString(),
            (std::vector<std::string>{"C0", "T0", "T1", "C1", "T2", "T3"}));
}

TEST_F(TabCollectionStorageTest, RemoveMixedTabAndCollectionOperation) {
  auto tab_collection_one = std::make_unique<tabs::UnpinnedTabCollection>();
  auto tab_collection_two = std::make_unique<tabs::UnpinnedTabCollection>();

  tabs::TabCollection* tab_collection_one_ptr = tab_collection_one.get();
  tabs::TabCollection* tab_collection_two_ptr = tab_collection_two.get();

  // This is the top level collection storage.
  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();

  // Add four more tabs.
  AddTabs(4);
  ResetStorageChildrenIDs(0);

  SetCollectionID(tab_collection_one_ptr, 0);
  SetCollectionID(tab_collection_two_ptr, 1);

  collection_storage->AddCollection(std::move(tab_collection_one), 2ul);
  collection_storage->AddCollection(std::move(tab_collection_two), 4ul);

  EXPECT_EQ(collection_storage->GetChildrenCount(), 6ul);

  auto removed_collection =
      collection_storage->RemoveCollection(tab_collection_one_ptr);
  EXPECT_EQ(removed_collection.get(), tab_collection_one_ptr);
  EXPECT_EQ(collection_storage->GetIndexOfCollection(tab_collection_two_ptr),
            3ul);
  EXPECT_FALSE(collection_storage->ContainsCollection(tab_collection_one_ptr));
  EXPECT_EQ(StorageCollectionChildrenString(),
            (std::vector<std::string>{"T0", "T1", "T2", "C1", "T3"}));
}

TEST_F(TabCollectionStorageTest, MoveMixedTabAndCollectionOperation) {
  auto tab_model_one =
      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();

  auto tab_collection_one = std::make_unique<tabs::UnpinnedTabCollection>();
  tabs::TabCollection* tab_collection_one_ptr = tab_collection_one.get();

  auto tab_collection_two = std::make_unique<tabs::UnpinnedTabCollection>();
  tabs::TabCollection* tab_collection_two_ptr = tab_collection_two.get();

  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();

  collection_storage->AddTab(std::move(tab_model_one), 0);
  AddTabs(4);
  ResetStorageChildrenIDs(0);

  collection_storage->AddCollection(std::move(tab_collection_one), 3ul);
  collection_storage->AddCollection(std::move(tab_collection_two), 1ul);
  EXPECT_EQ(collection_storage->GetChildrenCount(), 7ul);

  // Set collection ids of both the sub collections.
  SetCollectionID(tab_collection_one_ptr, 0);
  SetCollectionID(tab_collection_two_ptr, 1);

  // Move `collection_one` to index 1.
  collection_storage->MoveCollection(tab_collection_one_ptr, 1ul);
  EXPECT_EQ(collection_storage->GetIndexOfCollection(tab_collection_one_ptr),
            1ul);
  // Move `collection_two` to index 6.
  collection_storage->MoveCollection(tab_collection_two_ptr, 6ul);
  EXPECT_EQ(collection_storage->GetIndexOfCollection(tab_collection_two_ptr),
            6ul);
  // Move `tab_model_one` to index 6.
  collection_storage->MoveTab(tab_model_one_ptr, 6ul);

  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_one_ptr), 6ul);
  EXPECT_EQ(collection_storage->GetIndexOfCollection(tab_collection_one_ptr),
            0ul);
  EXPECT_EQ(collection_storage->GetIndexOfCollection(tab_collection_two_ptr),
            5ul);
  EXPECT_EQ(
      StorageCollectionChildrenString(),
      (std::vector<std::string>{"C0", "T1", "T2", "T3", "T4", "C1", "T0"}));
}