blob: 2ddc09b3a78574f1e987abb464418f258eabe87f [file] [log] [blame]
// Copyright 2021 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/saved_tab_groups/saved_tab_group_model.h"
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/guid.h"
#include "base/token.h"
#include "components/saved_tab_groups/saved_tab_group.h"
#include "components/saved_tab_groups/saved_tab_group_model_observer.h"
#include "components/saved_tab_groups/saved_tab_group_tab.h"
#include "components/sync/protocol/saved_tab_group_specifics.pb.h"
#include "components/tab_groups/tab_group_color.h"
#include "components/tab_groups/tab_group_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gfx/image/image.h"
#include "url/gurl.h"
#include "url/url_constants.h"
namespace {
base::GUID GenerateNextGUID() {
static uint64_t guid_increment;
if (!guid_increment) {
guid_increment = 0;
}
uint64_t kBytes[] = {0, guid_increment};
base::GUID guid =
base::GUID::ParseCaseInsensitive(base::RandomDataToGUIDString(kBytes));
guid_increment++;
return guid;
}
void CompareSavedTabGroupTabs(const std::vector<SavedTabGroupTab>& v1,
const std::vector<SavedTabGroupTab>& v2) {
ASSERT_EQ(v1.size(), v2.size());
for (size_t i = 0; i < v1.size(); i++) {
SavedTabGroupTab tab1 = v1[i];
SavedTabGroupTab tab2 = v2[i];
EXPECT_EQ(tab1.url(), tab2.url());
EXPECT_EQ(tab1.title(), tab2.title());
EXPECT_EQ(tab1.favicon(), tab2.favicon());
}
}
bool CompareSavedTabGroups(const SavedTabGroup& g1, const SavedTabGroup& g2) {
if (g1.title() != g2.title())
return false;
if (g1.color() != g2.color())
return false;
if (g1.position() != g2.position())
return false;
if (g1.saved_guid() != g2.saved_guid())
return false;
if (g1.creation_time_windows_epoch_micros() !=
g2.creation_time_windows_epoch_micros()) {
return false;
}
return true;
}
SavedTabGroup CreateSavedTabGroup(
const std::u16string& group_title,
const tab_groups::TabGroupColorId& color,
const std::vector<SavedTabGroupTab>& group_tabs,
const base::GUID& id) {
return SavedTabGroup(group_title, color, group_tabs, id);
}
SavedTabGroupTab CreateSavedTabGroupTab(const std::string& url,
const std::u16string& title,
const base::GUID& group_guid,
absl::optional<int> position) {
SavedTabGroupTab tab(GURL(url), title, group_guid, nullptr, absl::nullopt,
absl::nullopt, position);
tab.SetFavicon(gfx::Image());
return tab;
}
SavedTabGroup CreateTestSavedTabGroup() {
base::GUID id = GenerateNextGUID();
const std::u16string title = u"Test Test";
const tab_groups::TabGroupColorId& color = tab_groups::TabGroupColorId::kBlue;
SavedTabGroupTab tab1 =
CreateSavedTabGroupTab("www.google.com", u"Google", id, absl::nullopt);
SavedTabGroupTab tab2 =
CreateSavedTabGroupTab("chrome://newtab", u"new tab", id, absl::nullopt);
tab1.SetFavicon(gfx::Image());
tab2.SetFavicon(gfx::Image());
std::vector<SavedTabGroupTab> tabs = {tab1, tab2};
SavedTabGroup group(title, color, tabs, id);
return group;
}
} // namespace
// Serves to test the functions in SavedTabGroupModelObserver.
class SavedTabGroupModelObserverTest : public ::testing::Test,
public SavedTabGroupModelObserver {
protected:
SavedTabGroupModelObserverTest() = default;
~SavedTabGroupModelObserverTest() override = default;
void SetUp() override {
saved_tab_group_model_ = std::make_unique<SavedTabGroupModel>();
saved_tab_group_model_->AddObserver(this);
}
void TearDown() override { saved_tab_group_model_.reset(); }
void SavedTabGroupAddedLocally(const base::GUID& guid) override {
retrieved_group_.emplace_back(*saved_tab_group_model_->Get(guid));
retrieved_index_ = saved_tab_group_model_->GetIndexOf(guid).value_or(-1);
}
void SavedTabGroupRemovedLocally(
const SavedTabGroup* removed_group) override {
retrieved_guid_ = removed_group->saved_guid();
}
void SavedTabGroupUpdatedLocally(
const base::GUID& group_guid,
const absl::optional<base::GUID>& tab_guid = absl::nullopt) override {
retrieved_group_.emplace_back(*saved_tab_group_model_->Get(group_guid));
retrieved_index_ =
saved_tab_group_model_->GetIndexOf(group_guid).value_or(-1);
}
void SavedTabGroupAddedFromSync(const base::GUID& guid) override {
retrieved_group_.emplace_back(*saved_tab_group_model_->Get(guid));
retrieved_index_ = saved_tab_group_model_->GetIndexOf(guid).value_or(-1);
}
void SavedTabGroupRemovedFromSync(
const SavedTabGroup* removed_group) override {
retrieved_guid_ = removed_group->saved_guid();
}
void SavedTabGroupUpdatedFromSync(
const base::GUID& group_guid,
const absl::optional<base::GUID>& tab_guid = absl::nullopt) override {
retrieved_group_.emplace_back(*saved_tab_group_model_->Get(group_guid));
retrieved_index_ =
saved_tab_group_model_->GetIndexOf(group_guid).value_or(-1);
}
void SavedTabGroupReorderedLocally() override { reordered_called_ = true; }
void ClearSignals() {
retrieved_group_.clear();
retrieved_index_ = -1;
retrieved_old_index_ = -1;
retrieved_new_index_ = -1;
reordered_called_ = false;
retrieved_guid_ = base::GUID::GenerateRandomV4();
}
std::unique_ptr<SavedTabGroupModel> saved_tab_group_model_;
std::vector<SavedTabGroup> retrieved_group_;
int retrieved_index_ = -1;
int retrieved_old_index_ = -1;
int retrieved_new_index_ = -1;
bool reordered_called_ = false;
base::GUID retrieved_guid_ = base::GUID::GenerateRandomV4();
std::string base_path_ = "file:///c:/tmp/";
};
// Serves to test the functions in SavedTabGroupModel.
class SavedTabGroupModelTest : public ::testing::Test {
protected:
SavedTabGroupModelTest()
: id_1_(GenerateNextGUID()),
id_2_(GenerateNextGUID()),
id_3_(GenerateNextGUID()) {}
~SavedTabGroupModelTest() override { RemoveTestData(); }
void SetUp() override {
saved_tab_group_model_ = std::make_unique<SavedTabGroupModel>();
AddTestData();
}
void TearDown() override {
RemoveTestData();
saved_tab_group_model_.reset();
}
void AddTestData() {
const std::u16string title_1 = u"Group One";
const std::u16string title_2 = u"Another Group";
const std::u16string title_3 = u"The Three Musketeers";
const tab_groups::TabGroupColorId& color_1 =
tab_groups::TabGroupColorId::kGrey;
const tab_groups::TabGroupColorId& color_2 =
tab_groups::TabGroupColorId::kRed;
const tab_groups::TabGroupColorId& color_3 =
tab_groups::TabGroupColorId::kGreen;
std::vector<SavedTabGroupTab> group_1_tabs = {
CreateSavedTabGroupTab("A_Link", u"Only Tab", id_1_, 0)};
std::vector<SavedTabGroupTab> group_2_tabs = {
CreateSavedTabGroupTab("One_Link", u"One Of Two", id_2_, 0),
CreateSavedTabGroupTab("Two_Link", u"Second", id_2_, 1)};
std::vector<SavedTabGroupTab> group_3_tabs = {
CreateSavedTabGroupTab("Athos", u"All For One", id_3_, 0),
CreateSavedTabGroupTab("Porthos", u"And", id_3_, 1),
CreateSavedTabGroupTab("Aramis", u"One For All", id_3_, 2)};
saved_tab_group_model_->Add(
CreateSavedTabGroup(title_1, color_1, group_1_tabs, id_1_));
saved_tab_group_model_->Add(
CreateSavedTabGroup(title_2, color_2, group_2_tabs, id_2_));
saved_tab_group_model_->Add(
CreateSavedTabGroup(title_3, color_3, group_3_tabs, id_3_));
}
void RemoveTestData() {
if (!saved_tab_group_model_)
return;
// Copy ids so we do not remove elements while we are accessing the data.
std::vector<base::GUID> saved_tab_group_ids;
for (const SavedTabGroup& saved_group :
saved_tab_group_model_->saved_tab_groups()) {
saved_tab_group_ids.emplace_back(saved_group.saved_guid());
}
for (const auto& id : saved_tab_group_ids) {
saved_tab_group_model_->Remove(id);
}
}
std::unique_ptr<SavedTabGroupModel> saved_tab_group_model_;
std::string base_path_ = "file:///c:/tmp/";
base::GUID id_1_;
base::GUID id_2_;
base::GUID id_3_;
};
// Tests that SavedTabGroupModel::Count holds 3 elements initially.
TEST_F(SavedTabGroupModelTest, InitialCountThree) {
EXPECT_EQ(saved_tab_group_model_->Count(), 3);
EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), 3u);
}
// Tests that SavedTabGroupModel::Contains returns the 3, the number of starting
// ids added to the model.
TEST_F(SavedTabGroupModelTest, InitialGroupsAreSaved) {
EXPECT_TRUE(saved_tab_group_model_->Contains(id_1_));
EXPECT_TRUE(saved_tab_group_model_->Contains(id_2_));
EXPECT_TRUE(saved_tab_group_model_->Contains(id_3_));
EXPECT_FALSE(saved_tab_group_model_->Contains(GenerateNextGUID()));
}
// Tests that the SavedTabGroupModel::GetIndexOf preserves the order the
// SavedTabGroups were inserted into.
TEST_F(SavedTabGroupModelTest, InitialOrderAdded) {
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(id_1_), 0);
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(id_3_), 2);
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(id_2_), 1);
}
// Tests that the SavedTabGroupModel::IsEmpty has elements and once all elements
// are removed is empty.
TEST_F(SavedTabGroupModelTest, ContainsNoElementsOnRemoval) {
EXPECT_FALSE(saved_tab_group_model_->IsEmpty());
RemoveTestData();
EXPECT_TRUE(saved_tab_group_model_->IsEmpty());
}
// Tests that the SavedTabGroupModel::Remove removes the correct element given
// an id.
TEST_F(SavedTabGroupModelTest, RemovesCorrectElements) {
saved_tab_group_model_->Remove(id_3_);
EXPECT_FALSE(saved_tab_group_model_->Contains(id_3_));
EXPECT_TRUE(saved_tab_group_model_->Contains(id_2_));
EXPECT_TRUE(saved_tab_group_model_->Contains(id_1_));
}
// Tests that the SavedTabGroupModel only adds unique TabGroupIds.
TEST_F(SavedTabGroupModelTest, OnlyAddUniqueElements) {
EXPECT_EQ(saved_tab_group_model_->Count(), 3);
AddTestData();
EXPECT_EQ(saved_tab_group_model_->Count(), 3);
}
// Tests that SavedTabGroupModel::Add adds an extra element into the model and
// keeps the data.
TEST_F(SavedTabGroupModelTest, AddNewElement) {
base::GUID id_4 = GenerateNextGUID();
const std::u16string title_4 = u"Test Test";
const tab_groups::TabGroupColorId& color_4 =
tab_groups::TabGroupColorId::kBlue;
SavedTabGroupTab tab1 =
CreateSavedTabGroupTab("4th group", u"First Tab 4th Group", id_4, 0);
SavedTabGroupTab tab2 =
CreateSavedTabGroupTab("2nd link", u"Second Tab 4th Group", id_4, 1);
std::vector<SavedTabGroupTab> group_4_tabs = {tab1, tab2};
SavedTabGroup group_4(title_4, color_4, group_4_tabs, id_4);
saved_tab_group_model_->Add(group_4);
EXPECT_TRUE(saved_tab_group_model_->Contains(id_4));
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(id_4), 3);
EXPECT_EQ(saved_tab_group_model_->Count(), 4);
const SavedTabGroup* saved_group = saved_tab_group_model_->Get(id_4);
EXPECT_EQ(saved_group->saved_guid(), id_4);
EXPECT_EQ(saved_group->title(), title_4);
EXPECT_EQ(saved_group->color(), color_4);
CompareSavedTabGroupTabs(saved_group->saved_tabs(), group_4_tabs);
}
// Tests that SavedTabGroupModel::Update updates the correct element if the
// title or color are different.
TEST_F(SavedTabGroupModelTest, UpdateElement) {
const SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
const std::u16string original_title = group->title();
const tab_groups::TabGroupColorId& original_color = group->color();
// Should only update the element if title or color are different
const std::u16string same_title = u"Group One";
const tab_groups::TabGroupColorId& same_color =
tab_groups::TabGroupColorId::kGrey;
const tab_groups::TabGroupVisualData same_visual_data(same_title, same_color,
/*is_collapsed*/ false);
saved_tab_group_model_->UpdateVisualData(id_1_, &same_visual_data);
EXPECT_EQ(group->title(), original_title);
EXPECT_EQ(group->color(), original_color);
// Updates both color and title
const std::u16string new_title = u"New Title";
const tab_groups::TabGroupColorId& new_color =
tab_groups::TabGroupColorId::kCyan;
const tab_groups::TabGroupVisualData new_visual_data(new_title, new_color,
/*is_collapsed*/ false);
saved_tab_group_model_->UpdateVisualData(id_1_, &new_visual_data);
EXPECT_EQ(group->title(), new_title);
EXPECT_EQ(group->color(), new_color);
// Update only title
const std::u16string random_title = u"Random Title";
const tab_groups::TabGroupVisualData change_title_visual_data(
random_title, original_color, /*is_collapsed*/ false);
saved_tab_group_model_->UpdateVisualData(id_1_, &change_title_visual_data);
EXPECT_EQ(group->title(), random_title);
EXPECT_EQ(group->color(), original_color);
// Update only color
const tab_groups::TabGroupColorId& random_color =
tab_groups::TabGroupColorId::kGrey;
const tab_groups::TabGroupVisualData change_color_visual_data(
original_title, random_color, /*is_collapsed*/ false);
saved_tab_group_model_->UpdateVisualData(id_1_, &change_color_visual_data);
EXPECT_EQ(group->title(), original_title);
EXPECT_EQ(group->color(), random_color);
// Update update time
base::Time time = base::Time::Now();
saved_tab_group_model_->Get(id_1_)->SetUpdateTimeWindowsEpochMicros(time);
EXPECT_EQ(
time,
saved_tab_group_model_->Get(id_1_)->update_time_windows_epoch_micros());
}
// Tests that the correct tabs are added to the correct position in group 1.
TEST_F(SavedTabGroupModelTest, AddTabToGroup) {
SavedTabGroupTab tab1 =
CreateSavedTabGroupTab("4th group", u"First Tab 4th Group", id_1_, 0);
SavedTabGroupTab tab2 =
CreateSavedTabGroupTab("2nd link", u"Second Tab 4th Group", id_1_, 2);
SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
ASSERT_EQ(group->saved_tabs().size(), size_t(1));
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab1,
/*update_tab_positions=*/true);
EXPECT_EQ(group->saved_tabs().size(), size_t(2));
EXPECT_EQ(0, group->GetIndexOfTab(tab1.saved_tab_guid()));
EXPECT_TRUE(group->ContainsTab(tab1.saved_tab_guid()));
ASSERT_TRUE(group->GetTab(tab1.saved_tab_guid()));
CompareSavedTabGroupTabs({*group->GetTab(tab1.saved_tab_guid())}, {tab1});
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab2,
/*update_tab_positions=*/true);
EXPECT_EQ(group->saved_tabs().size(), size_t(3));
EXPECT_EQ(2, group->GetIndexOfTab(tab2.saved_tab_guid()));
EXPECT_TRUE(group->ContainsTab(tab2.saved_tab_guid()));
ASSERT_TRUE(group->GetTab(tab2.saved_tab_guid()));
CompareSavedTabGroupTabs({*group->GetTab(tab2.saved_tab_guid())}, {tab2});
CompareSavedTabGroupTabs(group->saved_tabs(),
{tab1, group->saved_tabs()[1], tab2});
}
// Tests that the correct tabs are removed from the correct position in group 1.
TEST_F(SavedTabGroupModelTest, RemoveTabFromGroup) {
SavedTabGroupTab tab1 =
CreateSavedTabGroupTab("4th group", u"First Tab 4th Group", id_1_, 0);
SavedTabGroupTab tab2 =
CreateSavedTabGroupTab("2nd link", u"Second Tab 4th Group", id_1_, 2);
SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
ASSERT_EQ(group->saved_tabs().size(), size_t(1));
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab1,
/*update_tab_positions=*/true);
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab2,
/*update_tab_positions=*/true);
EXPECT_EQ(group->saved_tabs().size(), size_t(3));
saved_tab_group_model_->RemoveTabFromGroup(group->saved_guid(),
tab1.saved_tab_guid(),
/*update_tab_positions=*/true);
EXPECT_EQ(group->saved_tabs().size(), size_t(2));
CompareSavedTabGroupTabs(group->saved_tabs(), {group->saved_tabs()[0], tab2});
saved_tab_group_model_->RemoveTabFromGroup(group->saved_guid(),
tab2.saved_tab_guid(),
/*update_tab_positions=*/true);
EXPECT_EQ(group->saved_tabs().size(), size_t(1));
CompareSavedTabGroupTabs(group->saved_tabs(), {group->saved_tabs()[0]});
}
// Tests that a group is removed from the model when the last tab is removed
// from it.
TEST_F(SavedTabGroupModelTest, RemoveLastTabFromGroup) {
SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
ASSERT_EQ(group->saved_tabs().size(), size_t(1));
saved_tab_group_model_->RemoveTabFromGroup(
group->saved_guid(), group->saved_tabs()[0].saved_tab_guid(),
/*update_tab_positions=*/true);
EXPECT_FALSE(saved_tab_group_model_->Contains(id_1_));
}
// Tests that the correct tabs are replaced in group 1.
TEST_F(SavedTabGroupModelTest, ReplaceTabInGroup) {
SavedTabGroupTab tab1 =
CreateSavedTabGroupTab("first", u"First Tab", id_1_, 0);
SavedTabGroupTab tab2 =
CreateSavedTabGroupTab("second", u"Second Tab", id_1_, 2);
SavedTabGroupTab tab3 =
CreateSavedTabGroupTab("third", u"Third Tab", id_1_, absl::nullopt);
SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
ASSERT_EQ(group->saved_tabs().size(), size_t(1));
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab1,
/*update_tab_positions=*/true);
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab2,
/*update_tab_positions=*/true);
EXPECT_EQ(group->saved_tabs().size(), size_t(3));
saved_tab_group_model_->ReplaceTabInGroupAt(group->saved_guid(),
tab1.saved_tab_guid(), tab3);
CompareSavedTabGroupTabs(group->saved_tabs(),
{tab3, group->saved_tabs()[1], tab2});
saved_tab_group_model_->ReplaceTabInGroupAt(group->saved_guid(),
tab2.saved_tab_guid(), tab1);
CompareSavedTabGroupTabs(group->saved_tabs(),
{tab3, group->saved_tabs()[1], tab1});
saved_tab_group_model_->ReplaceTabInGroupAt(
group->saved_guid(), group->saved_tabs()[1].saved_tab_guid(), tab2);
CompareSavedTabGroupTabs(group->saved_tabs(), {tab3, tab2, tab1});
}
// Tests that the correct tabs are moved in group 1.
TEST_F(SavedTabGroupModelTest, MoveTabInGroup) {
SavedTabGroupTab tab1 =
CreateSavedTabGroupTab("4th group", u"First Tab 4th Group", id_1_, 0);
SavedTabGroupTab tab2 =
CreateSavedTabGroupTab("2nd link", u"Second Tab 4th Group", id_1_, 2);
SavedTabGroup* group = saved_tab_group_model_->Get(id_1_);
ASSERT_EQ(group->saved_tabs().size(), size_t(1));
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab1,
/*update_tab_positions=*/true);
saved_tab_group_model_->AddTabToGroup(group->saved_guid(), tab2,
/*update_tab_positions=*/true);
EXPECT_EQ(group->saved_tabs().size(), size_t(3));
saved_tab_group_model_->MoveTabInGroupTo(group->saved_guid(),
tab1.saved_tab_guid(), 2);
CompareSavedTabGroupTabs(group->saved_tabs(),
{group->saved_tabs()[0], tab2, tab1});
saved_tab_group_model_->MoveTabInGroupTo(group->saved_guid(),
tab1.saved_tab_guid(), 1);
CompareSavedTabGroupTabs(group->saved_tabs(),
{group->saved_tabs()[0], tab1, tab2});
}
TEST_F(SavedTabGroupModelTest, MoveElement) {
EXPECT_EQ(1, saved_tab_group_model_->GetIndexOf(id_2_));
saved_tab_group_model_->Reorder(id_2_, 2);
EXPECT_EQ(2, saved_tab_group_model_->GetIndexOf(id_2_));
saved_tab_group_model_->Reorder(id_2_, 0);
EXPECT_EQ(0, saved_tab_group_model_->GetIndexOf(id_2_));
}
TEST_F(SavedTabGroupModelTest, LoadStoredEntriesPopulatesModel) {
std::unique_ptr<SavedTabGroup> group =
std::make_unique<SavedTabGroup>(*saved_tab_group_model_->Get(id_3_));
std::vector<sync_pb::SavedTabGroupSpecifics> specifics;
specifics.emplace_back(*group->ToSpecifics());
for (SavedTabGroupTab tab : group->saved_tabs())
specifics.emplace_back(*tab.ToSpecifics());
EXPECT_EQ(specifics.size(), size_t(4));
saved_tab_group_model_->Remove(id_3_);
saved_tab_group_model_->LoadStoredEntries(specifics);
EXPECT_TRUE(saved_tab_group_model_->Contains(id_3_));
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(id_3_), 2);
EXPECT_EQ(saved_tab_group_model_->Count(), 3);
const SavedTabGroup* saved_group = saved_tab_group_model_->Get(id_3_);
EXPECT_EQ(saved_group->saved_guid(), id_3_);
EXPECT_EQ(saved_group->title(), group->title());
EXPECT_EQ(saved_group->color(), group->color());
// We can not use CompareSavedTabGroupTabs because the favicons are not loaded
// until the tab is opened through the saved group button.
EXPECT_EQ(saved_group->saved_tabs().size(), group->saved_tabs().size());
}
// Tests that merging a group with the same group_id changes the state of the
// object correctly.
TEST_F(SavedTabGroupModelTest, MergeGroupsFromModel) {
SavedTabGroup* group1 = saved_tab_group_model_->Get(id_1_);
SavedTabGroup group2 = SavedTabGroup::FromSpecifics(*group1->ToSpecifics());
group2.SetColor(tab_groups::TabGroupColorId::kPink);
group2.SetTitle(u"Updated title");
SavedTabGroup merged_group = SavedTabGroup::FromSpecifics(
*saved_tab_group_model_->MergeGroup(*group2.ToSpecifics()));
EXPECT_EQ(group2.title(), merged_group.title());
EXPECT_EQ(group2.color(), merged_group.color());
EXPECT_EQ(group2.saved_guid(), merged_group.saved_guid());
EXPECT_EQ(group2.creation_time_windows_epoch_micros(),
merged_group.creation_time_windows_epoch_micros());
EXPECT_EQ(group2.update_time_windows_epoch_micros(),
merged_group.update_time_windows_epoch_micros());
}
// Tests that merging a tab with the same tab_id changes the state of the object
// correctly.
TEST_F(SavedTabGroupModelTest, MergeTabsFromModel) {
SavedTabGroupTab tab1 = saved_tab_group_model_->Get(id_1_)->saved_tabs()[0];
SavedTabGroupTab tab2 = SavedTabGroupTab::FromSpecifics(*tab1.ToSpecifics());
tab2.SetTitle(u"Updated Title");
tab2.SetURL(GURL("chrome://updated_url"));
SavedTabGroupTab merged_tab = SavedTabGroupTab::FromSpecifics(
*saved_tab_group_model_->MergeTab(*tab2.ToSpecifics()));
EXPECT_EQ(tab2.url(), merged_tab.url());
EXPECT_EQ(tab2.saved_tab_guid(), merged_tab.saved_tab_guid());
EXPECT_EQ(tab2.saved_group_guid(), merged_tab.saved_group_guid());
EXPECT_EQ(tab2.creation_time_windows_epoch_micros(),
merged_tab.creation_time_windows_epoch_micros());
EXPECT_EQ(tab2.update_time_windows_epoch_micros(),
merged_tab.update_time_windows_epoch_micros());
}
// Tests that groups inserted in the model are in order stay inserted in sorted
// order.
TEST_F(SavedTabGroupModelTest, GroupsSortedWithInOrderPositions) {
RemoveTestData();
// Create an arbitrary number of groups.
SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
// Set the positions the groups should sit in the bookmarks bar.
group_1.SetPosition(0);
group_2.SetPosition(1);
group_3.SetPosition(2);
group_4.SetPosition(3);
group_5.SetPosition(4);
group_6.SetPosition(5);
// This is the order we expect the groups in the model to be.
std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
group_4, group_5, group_6};
// Add the groups into the model in order.
saved_tab_group_model_->Add(group_1);
saved_tab_group_model_->Add(group_2);
saved_tab_group_model_->Add(group_3);
saved_tab_group_model_->Add(group_4);
saved_tab_group_model_->Add(group_5);
saved_tab_group_model_->Add(group_6);
EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
for (size_t i = 0; i < groups.size(); ++i) {
EXPECT_TRUE(CompareSavedTabGroups(
groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
}
}
// Tests that groups inserted in the model out of order are still inserted in
// sorted order.
TEST_F(SavedTabGroupModelTest, GroupsSortedWithOutOfOrderPositions) {
RemoveTestData();
// Create an arbitrary number of groups.
SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
// Set the positions the groups should sit in the bookmarks bar.
group_1.SetPosition(0);
group_2.SetPosition(1);
group_3.SetPosition(2);
group_4.SetPosition(3);
group_5.SetPosition(4);
group_6.SetPosition(5);
// This is the order we expect the groups in the model to be.
std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
group_4, group_5, group_6};
// Add the groups into the model in an arbitrary order.
saved_tab_group_model_->Add(group_6);
saved_tab_group_model_->Add(group_1);
saved_tab_group_model_->Add(group_4);
saved_tab_group_model_->Add(group_3);
saved_tab_group_model_->Add(group_5);
saved_tab_group_model_->Add(group_2);
EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
for (size_t i = 0; i < groups.size(); ++i) {
EXPECT_TRUE(CompareSavedTabGroups(
groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
}
}
// Tests that groups inserted in the model with gaps between the positions are
// still inserted in sorted order.
TEST_F(SavedTabGroupModelTest, GroupsSortedWithGapsInPositions) {
RemoveTestData();
// Create an arbitrary number of groups.
SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
// Set the positions the groups should sit in the bookmarks bar.
group_1.SetPosition(0);
group_2.SetPosition(3);
group_3.SetPosition(8);
group_4.SetPosition(19);
group_5.SetPosition(21);
group_6.SetPosition(34);
// This is the order we expect the groups in the model to be.
std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
group_4, group_5, group_6};
// Add the groups into the model in an arbitrary order.
saved_tab_group_model_->Add(group_6);
saved_tab_group_model_->Add(group_1);
saved_tab_group_model_->Add(group_4);
saved_tab_group_model_->Add(group_3);
saved_tab_group_model_->Add(group_5);
saved_tab_group_model_->Add(group_2);
EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
for (size_t i = 0; i < groups.size(); ++i) {
EXPECT_TRUE(CompareSavedTabGroups(
groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
}
}
// Tests that groups inserted in the model with gaps and in decreasing order
// between the positions are still inserted in increasing sorted order.
TEST_F(SavedTabGroupModelTest, GroupsSortedWithDecreasingPositions) {
RemoveTestData();
// Create an arbitrary number of groups.
SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
// Set the positions the groups should sit in the bookmarks bar.
group_1.SetPosition(0);
group_2.SetPosition(3);
group_3.SetPosition(8);
group_4.SetPosition(19);
group_5.SetPosition(21);
group_6.SetPosition(34);
// This is the order we expect the groups in the model to be.
std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
group_4, group_5, group_6};
// Add the groups into the model in an arbitrary order.
saved_tab_group_model_->Add(group_6);
saved_tab_group_model_->Add(group_5);
saved_tab_group_model_->Add(group_4);
saved_tab_group_model_->Add(group_3);
saved_tab_group_model_->Add(group_2);
saved_tab_group_model_->Add(group_1);
EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
for (size_t i = 0; i < groups.size(); ++i) {
EXPECT_TRUE(CompareSavedTabGroups(
groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
}
}
// Tests that groups inserted in the model with a more recent update time take
// precedence over groups with the same position.
TEST_F(SavedTabGroupModelTest, GroupWithSamePositionSortedByUpdateTime) {
RemoveTestData();
// Create an arbitrary number of groups.
SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
// Set the positions the groups should sit in the bookmarks bar.
group_1.SetPosition(0);
group_2.SetPosition(0);
// This is the order we expect the groups in the model to be.
std::vector<SavedTabGroup> groups = {group_2, group_1};
// Add the groups into the model in an arbitrary order.
saved_tab_group_model_->Add(group_1);
saved_tab_group_model_->Add(group_2);
EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
for (size_t i = 0; i < groups.size(); ++i) {
EXPECT_TRUE(CompareSavedTabGroups(
groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
}
}
// Tests that groups inserted in the model with no position are inserted at the
// back of the model and have their position set to the last index at the time
// they were inserted.
TEST_F(SavedTabGroupModelTest, GroupsWithNoPositionInsertedAtEnd) {
RemoveTestData();
// Create an arbitrary number of groups.
SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
// Set the positions the groups should sit in the bookmarks bar.
group_1.SetPosition(0);
group_2.SetPosition(1);
group_3.SetPosition(2);
group_4.SetPosition(3);
group_5.SetPosition(4);
// This is the order we expect the groups in the model to be.
std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
group_4, group_5, group_6};
// Add the groups into the model in an arbitrary order.
saved_tab_group_model_->Add(group_1);
saved_tab_group_model_->Add(group_2);
saved_tab_group_model_->Add(group_3);
saved_tab_group_model_->Add(group_4);
saved_tab_group_model_->Add(group_5);
saved_tab_group_model_->Add(group_6);
groups[5].SetPosition(5);
EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
// Expect the 6th group to have a position of 5 (0-based indexing).
EXPECT_EQ(saved_tab_group_model_
->saved_tab_groups()
[saved_tab_group_model_->saved_tab_groups().size() - 1]
.position(),
groups[5].position());
for (size_t i = 0; i < groups.size(); ++i) {
EXPECT_TRUE(CompareSavedTabGroups(
groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
}
}
// Tests that SavedTabGroupModelObserver::Added passes the correct element from
// the model.
TEST_F(SavedTabGroupModelObserverTest, AddElement) {
SavedTabGroup group_4(CreateTestSavedTabGroup());
saved_tab_group_model_->Add(group_4);
const int index = retrieved_group_.size() - 1;
ASSERT_GE(index, 0);
SavedTabGroup received_group = retrieved_group_[index];
EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
EXPECT_EQ(group_4.title(), received_group.title());
EXPECT_EQ(group_4.color(), received_group.color());
CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
retrieved_index_);
}
// Tests that SavedTabGroupModelObserver::Removed passes the correct
// element from the model.
TEST_F(SavedTabGroupModelObserverTest, RemovedElement) {
SavedTabGroup group_4(CreateTestSavedTabGroup());
saved_tab_group_model_->Add(group_4);
saved_tab_group_model_->Remove(group_4.saved_guid());
EXPECT_EQ(group_4.saved_guid(), retrieved_guid_);
EXPECT_FALSE(saved_tab_group_model_->Contains(retrieved_guid_));
// The model will have already removed and sent the index our element was at
// before it was removed from the model. As such, we should get -1 when
// checking the model and 0 for the retrieved index.
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(retrieved_guid_), absl::nullopt);
}
// Tests that SavedTabGroupModelObserver::Updated passes the correct
// element from the model.
TEST_F(SavedTabGroupModelObserverTest, UpdatedElement) {
SavedTabGroup group_4(CreateTestSavedTabGroup());
saved_tab_group_model_->Add(group_4);
const std::u16string new_title = u"New Title";
const tab_groups::TabGroupColorId& new_color =
tab_groups::TabGroupColorId::kBlue;
const tab_groups::TabGroupVisualData new_visual_data(new_title, new_color,
/*is_collapsed*/ false);
saved_tab_group_model_->UpdateVisualData(group_4.saved_guid(),
&new_visual_data);
const int index = retrieved_group_.size() - 1;
ASSERT_GE(index, 0);
SavedTabGroup received_group = retrieved_group_[index];
EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
EXPECT_EQ(new_title, received_group.title());
EXPECT_EQ(new_color, received_group.color());
CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
retrieved_index_);
}
// Tests that SavedTabGroupModelObserver::AddedFromSync passes the correct
// element from the model.
TEST_F(SavedTabGroupModelObserverTest, AddElementFromSync) {
SavedTabGroup group_4(CreateTestSavedTabGroup());
saved_tab_group_model_->AddedFromSync(group_4);
const int index = retrieved_group_.size() - 1;
ASSERT_GE(index, 0);
SavedTabGroup received_group = retrieved_group_[index];
EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
EXPECT_EQ(group_4.title(), received_group.title());
EXPECT_EQ(group_4.color(), received_group.color());
CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
retrieved_index_);
}
// Tests that SavedTabGroupModelObserver::RemovedFromSync passes the correct
// element from the model.
TEST_F(SavedTabGroupModelObserverTest, RemovedElementFromSync) {
SavedTabGroup group_4(CreateTestSavedTabGroup());
saved_tab_group_model_->Add(group_4);
saved_tab_group_model_->RemovedFromSync(group_4.saved_guid());
EXPECT_EQ(group_4.saved_guid(), retrieved_guid_);
EXPECT_FALSE(saved_tab_group_model_->Contains(retrieved_guid_));
// The model will have already removed and sent the index our element was at
// before it was removed from the model. As such, we should get -1 when
// checking the model and 0 for the retrieved index.
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(retrieved_guid_), absl::nullopt);
}
// Tests that SavedTabGroupModelObserver::UpdatedFromSync passes the correct
// element from the model.
TEST_F(SavedTabGroupModelObserverTest, UpdatedElementFromSync) {
SavedTabGroup group_4(CreateTestSavedTabGroup());
saved_tab_group_model_->Add(group_4);
const std::u16string new_title = u"New Title";
const tab_groups::TabGroupColorId& new_color =
tab_groups::TabGroupColorId::kBlue;
const tab_groups::TabGroupVisualData new_visual_data(new_title, new_color,
/*is_collapsed*/ false);
saved_tab_group_model_->UpdatedVisualDataFromSync(group_4.saved_guid(),
&new_visual_data);
const int index = retrieved_group_.size() - 1;
ASSERT_GE(index, 0);
SavedTabGroup received_group = retrieved_group_[index];
EXPECT_EQ(group_4.local_group_id(), received_group.local_group_id());
EXPECT_EQ(new_title, received_group.title());
EXPECT_EQ(new_color, received_group.color());
CompareSavedTabGroupTabs(group_4.saved_tabs(), received_group.saved_tabs());
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(received_group.saved_guid()),
retrieved_index_);
}
// Verify that SavedTabGroupModel::OnGroupClosedInTabStrip passes the correct
// index.
TEST_F(SavedTabGroupModelObserverTest, OnGroupClosedInTabStrip) {
SavedTabGroup group_4 = CreateTestSavedTabGroup();
tab_groups::TabGroupId tab_group_id = tab_groups::TabGroupId::GenerateNew();
group_4.SetLocalGroupId(tab_group_id);
saved_tab_group_model_->Add(group_4);
const int index =
saved_tab_group_model_->GetIndexOf(group_4.saved_guid()).value();
ASSERT_GE(index, 0);
// Expect the saved group that calls update is the one that was removed from
// the tabstrip.
saved_tab_group_model_->OnGroupClosedInTabStrip(
group_4.local_group_id().value());
EXPECT_EQ(index, retrieved_index_);
// Expect the removal of group_4 from the tabstrip makes GetIndexOf not return
// a valid index when searched by tab group id, but does return the right
// index when searched by saved guid.
saved_tab_group_model_->OnGroupClosedInTabStrip(tab_group_id);
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(tab_group_id), absl::nullopt);
EXPECT_EQ(saved_tab_group_model_->GetIndexOf(group_4.saved_guid()), index);
}
// Tests that SavedTabGroupModelObserver::Moved passes the correct
// element from the model.
TEST_F(SavedTabGroupModelObserverTest, MoveElement) {
SavedTabGroup stg_1(std::u16string(u"stg_1"),
tab_groups::TabGroupColorId::kGrey, {},
GenerateNextGUID());
SavedTabGroup stg_2(std::u16string(u"stg_2"),
tab_groups::TabGroupColorId::kGrey, {},
GenerateNextGUID());
SavedTabGroup stg_3(std::u16string(u"stg_3"),
tab_groups::TabGroupColorId::kGrey, {},
GenerateNextGUID());
saved_tab_group_model_->Add(stg_1);
saved_tab_group_model_->Add(stg_2);
saved_tab_group_model_->Add(stg_3);
saved_tab_group_model_->Reorder(stg_2.saved_guid(), 2);
EXPECT_TRUE(reordered_called_);
EXPECT_EQ(2, saved_tab_group_model_->GetIndexOf(stg_2.saved_guid()));
}
TEST_F(SavedTabGroupModelObserverTest, GetGroupContainingTab) {
// Add a non matching SavedTabGroup.
saved_tab_group_model_->Add(CreateTestSavedTabGroup());
// Add a matching group/tab and save the ids used for GetGroupContainingTab.
SavedTabGroup matching_group = CreateTestSavedTabGroup();
base::GUID matching_group_guid = matching_group.saved_guid();
base::GUID matching_tab_guid = GenerateNextGUID();
base::Token matching_local_tab_id = base::Token::CreateRandom();
SavedTabGroupTab tab(GURL(url::kAboutBlankURL), std::u16string(u"title"),
matching_group.saved_guid(), &matching_group,
matching_tab_guid, matching_local_tab_id);
matching_group.AddTab(std::move(tab));
saved_tab_group_model_->Add(std::move(matching_group));
// Add another non matching SavedTabGroup.
saved_tab_group_model_->Add(CreateTestSavedTabGroup());
ASSERT_EQ(3, saved_tab_group_model_->Count());
// call GetGroupContainingTab with the 2 ids and expect them to return.
EXPECT_EQ(saved_tab_group_model_->Get(matching_group_guid),
saved_tab_group_model_->GetGroupContainingTab(matching_tab_guid));
EXPECT_EQ(
saved_tab_group_model_->Get(matching_group_guid),
saved_tab_group_model_->GetGroupContainingTab(matching_local_tab_id));
// Expect GetGroupContainingTab to return null when there is no match.
EXPECT_EQ(nullptr,
saved_tab_group_model_->GetGroupContainingTab(GenerateNextGUID()));
EXPECT_EQ(nullptr,
saved_tab_group_model_->GetGroupContainingTab(base::Token()));
}