blob: 556d14f336802f8d77c2fc05400ee5a52cea0903 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
#include <memory>
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/icu_test_util.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
#include "chrome/browser/ui/views/side_panel/side_panel.h"
#include "chrome/browser/ui/views/side_panel/side_panel_combobox_model.h"
#include "chrome/browser/ui/views/side_panel/side_panel_content_proxy.h"
#include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/views/side_panel/side_panel_entry_observer.h"
#include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
#include "chrome/browser/ui/views/side_panel/side_panel_util.h"
#include "chrome/browser/ui/views/side_panel/side_panel_view_state_observer.h"
#include "chrome/common/pref_names.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/test/views_test_utils.h"
using testing::_;
namespace {
// Creates a basic SidePanelEntry for the given `key` that returns an empty view
// when shown.
std::unique_ptr<SidePanelEntry> CreateEntry(const SidePanelEntry::Key& key) {
return std::make_unique<SidePanelEntry>(
key, u"basic entry",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() { return std::make_unique<views::View>(); }));
}
} // namespace
class SidePanelCoordinatorTest : public TestWithBrowserView {
public:
void SetUp() override {
TestWithBrowserView::SetUp();
AddTab(browser_view()->browser(), GURL("http://foo1.com"));
AddTab(browser_view()->browser(), GURL("http://foo2.com"));
// Add a kSideSearch entry to the contextual registry for the first tab.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
content::WebContents* active_contents =
browser_view()->GetActiveWebContents();
auto* registry = SidePanelRegistry::Get(active_contents);
registry->Register(std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kSideSearch, u"testing1",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() { return std::make_unique<views::View>(); })));
contextual_registries_.push_back(registry);
// Add a kLens entry to the contextual registry for the second tab.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
active_contents = browser_view()->GetActiveWebContents();
registry = SidePanelRegistry::Get(active_contents);
registry->Register(std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kLens, u"testing1",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() { return std::make_unique<views::View>(); })));
contextual_registries_.push_back(SidePanelRegistry::Get(active_contents));
// Add a kSideSearch entry to the contextual registry for the second tab.
registry->Register(std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kSideSearch, u"testing1",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() { return std::make_unique<views::View>(); })));
coordinator_ = browser_view()->side_panel_coordinator();
coordinator_->SetNoDelaysForTesting();
global_registry_ = coordinator_->global_registry_;
// Verify the first tab has one entry, kSideSearch.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
active_contents = browser_view()->GetActiveWebContents();
SidePanelRegistry* contextual_registry =
SidePanelRegistry::Get(active_contents);
EXPECT_EQ(contextual_registry->entries().size(), 1u);
EXPECT_EQ(contextual_registry->entries()[0]->key().id(),
SidePanelEntry::Id::kSideSearch);
// Verify the second tab has 2 entries, kLens and kSideSearch.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
active_contents = browser_view()->GetActiveWebContents();
contextual_registry = SidePanelRegistry::Get(active_contents);
EXPECT_EQ(contextual_registry->entries().size(), 2u);
EXPECT_EQ(contextual_registry->entries()[0]->key().id(),
SidePanelEntry::Id::kLens);
EXPECT_EQ(contextual_registry->entries()[1]->key().id(),
SidePanelEntry::Id::kSideSearch);
}
void VerifyEntryExistanceAndValue(absl::optional<SidePanelEntry*> entry,
SidePanelEntry::Id id) {
EXPECT_TRUE(entry.has_value());
EXPECT_EQ(entry.value()->key().id(), id);
}
void VerifyEntryExistanceAndValue(absl::optional<SidePanelEntry::Id> entry,
SidePanelEntry::Id id) {
EXPECT_TRUE(entry.has_value());
EXPECT_EQ(entry.value(), id);
}
absl::optional<SidePanelEntry::Key> GetLastActiveEntryKey() {
return coordinator_->GetLastActiveEntryKey();
}
absl::optional<SidePanelEntry::Key> GetLastActiveGlobalEntryKey() {
return coordinator_->last_active_global_entry_key_;
}
absl::optional<SidePanelEntry::Key> GetSelectedKey() {
return coordinator_->GetSelectedKey();
}
bool ComboboxViewExists() {
return coordinator_->header_combobox_ != nullptr;
}
protected:
raw_ptr<SidePanelCoordinator> coordinator_;
raw_ptr<SidePanelRegistry> global_registry_;
std::vector<raw_ptr<SidePanelRegistry>> contextual_registries_;
};
class MockSidePanelViewStateObserver : public SidePanelViewStateObserver {
public:
MOCK_METHOD(void, OnSidePanelDidClose, (), (override));
};
TEST_F(SidePanelCoordinatorTest, ToggleSidePanel) {
coordinator_->Toggle();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
coordinator_->Toggle();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
}
TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidth) {
// Set side panel to right-aligned
browser_view()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kSidePanelHorizontalAlignment, true);
coordinator_->Toggle();
const int starting_width = 500;
browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
const int increment = 50;
browser_view()->unified_side_panel()->OnResize(increment, true);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(),
starting_width - increment);
// Set side panel to left-aligned
browser_view()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kSidePanelHorizontalAlignment, false);
browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
browser_view()->unified_side_panel()->OnResize(increment, true);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(),
starting_width + increment);
}
TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthMaxMin) {
coordinator_->Toggle();
const int starting_width = 500;
browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
// Use an increment large enough to hit side panel and browser contents
// minimum width constraints.
const int large_increment = 1000000000;
browser_view()->unified_side_panel()->OnResize(large_increment, true);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(),
browser_view()->unified_side_panel()->GetMinimumSize().width());
browser_view()->unified_side_panel()->OnResize(-large_increment, true);
views::test::RunScheduledLayout(browser_view());
BrowserViewLayout* layout_manager =
static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
const int min_web_contents_width =
layout_manager->GetMinWebContentsWidthForTesting();
EXPECT_EQ(browser_view()->contents_web_view()->width(),
min_web_contents_width);
}
TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthRTL) {
// Set side panel to right-aligned
browser_view()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kSidePanelHorizontalAlignment, true);
// Set UI direction to LTR
base::i18n::SetRTLForTesting(false);
coordinator_->Toggle();
const int starting_width = 500;
browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
const int increment = 50;
browser_view()->unified_side_panel()->OnResize(increment, true);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(),
starting_width - increment);
// Set UI direction to RTL
base::i18n::SetRTLForTesting(true);
browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
browser_view()->unified_side_panel()->OnResize(increment, true);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(),
starting_width + increment);
}
TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthWindowResize) {
coordinator_->Toggle();
const int starting_width = 500;
browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
views::test::RunScheduledLayout(browser_view());
EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
// Shrink browser window enough that side panel should also shrink in
// observance of web contents minimum width.
gfx::Rect original_bounds(browser_view()->GetBounds());
gfx::Size new_size(starting_width, starting_width);
gfx::Rect new_bounds(original_bounds);
new_bounds.set_size(new_size);
// Explicitly restore the browser window on ChromeOS, as it would otherwise
// be maximized and the SetBounds call would be a no-op.
#if BUILDFLAG(IS_CHROMEOS_ASH)
browser_view()->Restore();
#endif
browser_view()->SetBounds(new_bounds);
EXPECT_LT(browser_view()->unified_side_panel()->width(), starting_width);
BrowserViewLayout* layout_manager =
static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
const int min_web_contents_width =
layout_manager->GetMinWebContentsWidthForTesting();
EXPECT_EQ(browser_view()->contents_web_view()->width(),
min_web_contents_width);
// Return browser window to original size, side panel should also return to
// size prior to window resize.
browser_view()->SetBounds(original_bounds);
EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
}
TEST_F(SidePanelCoordinatorTest, ChangeSidePanelAlignment) {
browser_view()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kSidePanelHorizontalAlignment, true);
EXPECT_TRUE(browser_view()->unified_side_panel()->IsRightAligned());
EXPECT_EQ(browser_view()->unified_side_panel()->GetHorizontalAlignment(),
SidePanel::kAlignRight);
browser_view()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kSidePanelHorizontalAlignment, false);
EXPECT_FALSE(browser_view()->unified_side_panel()->IsRightAligned());
EXPECT_EQ(browser_view()->unified_side_panel()->GetHorizontalAlignment(),
SidePanel::kAlignLeft);
}
// Verify that right and left alignment works the same as when in LTR mode.
TEST_F(SidePanelCoordinatorTest, ChangeSidePanelAlignmentRTL) {
// Forcing the language to hebrew causes the ui to enter RTL mode.
base::test::ScopedRestoreICUDefaultLocale scoped_locale_("he");
browser_view()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kSidePanelHorizontalAlignment, true);
EXPECT_TRUE(browser_view()->unified_side_panel()->IsRightAligned());
EXPECT_EQ(browser_view()->unified_side_panel()->GetHorizontalAlignment(),
SidePanel::kAlignRight);
browser_view()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kSidePanelHorizontalAlignment, false);
EXPECT_FALSE(browser_view()->unified_side_panel()->IsRightAligned());
EXPECT_EQ(browser_view()->unified_side_panel()->GetHorizontalAlignment(),
SidePanel::kAlignLeft);
}
TEST_F(SidePanelCoordinatorTest,
ClosingSidePanelCallsOnSidePanelClosedObserver) {
MockSidePanelViewStateObserver view_state_observer;
EXPECT_CALL(view_state_observer, OnSidePanelDidClose()).Times(1);
coordinator_->AddSidePanelViewStateObserver(&view_state_observer);
coordinator_->Show();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
coordinator_->Close();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
}
TEST_F(SidePanelCoordinatorTest, RemovingObserverDoesNotIncrementCount) {
MockSidePanelViewStateObserver view_state_observer;
EXPECT_CALL(view_state_observer, OnSidePanelDidClose()).Times(1);
coordinator_->AddSidePanelViewStateObserver(&view_state_observer);
coordinator_->Show();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
coordinator_->Close();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
coordinator_->Show();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
coordinator_->RemoveSidePanelViewStateObserver(&view_state_observer);
coordinator_->Close();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
}
TEST_F(SidePanelCoordinatorTest, SidePanelReopensToLastSeenGlobalEntry) {
coordinator_->Toggle();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
coordinator_->Show(SidePanelEntry::Id::kBookmarks);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
coordinator_->Toggle();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
coordinator_->Toggle();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
}
TEST_F(SidePanelCoordinatorTest, ShowOpensSidePanel) {
coordinator_->Show(SidePanelEntry::Id::kBookmarks);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
// Verify that the combobox entry for bookmarks is selected.
EXPECT_EQ(GetSelectedKey().value().id(), SidePanelEntry::Id::kBookmarks);
}
TEST_F(SidePanelCoordinatorTest, CloseInvalidatesComboboxPointer) {
// Verify no combobox exists before opening the side panel.
EXPECT_FALSE(ComboboxViewExists());
coordinator_->Toggle();
EXPECT_TRUE(ComboboxViewExists());
// Verify that the pointer to the combobox view is invalidated after closing
// the side panel.
coordinator_->Toggle();
EXPECT_FALSE(ComboboxViewExists());
}
TEST_F(SidePanelCoordinatorTest, TabSwitchInvalidatesComboboxPointerOnClose) {
// Verify no combobox exists before opening the side panel.
EXPECT_FALSE(ComboboxViewExists());
// Show a contextual entry on the first tab.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
// Switch to the second tab.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
// Expect that the side panel closes.
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
// Verify that the pointer to the combobox view is invalidated after closing
// the side panel.
EXPECT_FALSE(ComboboxViewExists());
}
TEST_F(SidePanelCoordinatorTest, SwapBetweenTabsWithReadingListOpen) {
// Verify side panel opens to kReadingList by default.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Toggle();
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
// Verify switching tabs does not change side panel visibility or entry seen
// if it is in the global registry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
}
TEST_F(SidePanelCoordinatorTest, SwapBetweenTabsWithBookmarksOpen) {
// Open side panel and switch to kBookmarks and verify the active entry is
// updated.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Toggle();
coordinator_->Show(SidePanelEntry::Id::kBookmarks);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
// Verify switching tabs does not change entry seen if it is in the global
// registry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
}
TEST_F(SidePanelCoordinatorTest, ContextualEntryDeregistered) {
// Verify the first tab has one entry, kSideSearch.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_EQ(contextual_registries_[0]->entries().size(), 1u);
EXPECT_EQ(contextual_registries_[0]->entries()[0]->key().id(),
SidePanelEntry::Id::kSideSearch);
// Deregister kSideSearch from the first tab.
contextual_registries_[0]->Deregister(
SidePanelEntry::Key(SidePanelEntry::Id::kSideSearch));
EXPECT_EQ(contextual_registries_[0]->entries().size(), 0u);
}
TEST_F(SidePanelCoordinatorTest, ContextualEntryDeregisteredWhileVisible) {
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kReadingList);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Deregister kSideSearch from the first tab.
contextual_registries_[0]->Deregister(
SidePanelEntry::Key(SidePanelEntry::Id::kSideSearch));
EXPECT_EQ(contextual_registries_[0]->entries().size(), 0u);
// Verify the panel defaults back to the last visible global entry or the
// reading list.
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
}
// Test that the side panel closes if a contextual entry is deregistered while
// visible when no global entries have been shown since the panel was opened.
TEST_F(
SidePanelCoordinatorTest,
ContextualEntryDeregisteredWhileVisibleClosesPanelIfNoLastSeenGlobalEntryExists) {
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Deregister kSideSearch from the first tab.
contextual_registries_[0]->Deregister(
SidePanelEntry::Key(SidePanelEntry::Id::kSideSearch));
EXPECT_EQ(contextual_registries_[0]->entries().size(), 0u);
// Verify the panel closes.
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_FALSE(GetLastActiveEntryKey().has_value());
EXPECT_FALSE(global_registry_->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
}
TEST_F(SidePanelCoordinatorTest, ShowContextualEntry) {
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
}
TEST_F(SidePanelCoordinatorTest, SwapBetweenTwoContextualEntryWithTheSameId) {
// Open side search for the first tab.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kReadingList);
auto* reading_list_entry = coordinator_->GetCurrentSidePanelEntryForTesting();
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
auto* side_search_entry1 = coordinator_->GetCurrentSidePanelEntryForTesting();
// Switch to the second tab and open side search.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_EQ(reading_list_entry,
coordinator_->GetCurrentSidePanelEntryForTesting());
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_NE(side_search_entry1,
coordinator_->GetCurrentSidePanelEntryForTesting());
// Switch back to the first tab.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_EQ(side_search_entry1,
coordinator_->GetCurrentSidePanelEntryForTesting());
}
TEST_F(SidePanelCoordinatorTest,
SwapBetweenTabsAfterNavigatingToContextualEntry) {
// Open side panel and verify it opens to kReadingList by default.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Toggle();
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to a different global entry and verify the active entry is updated.
coordinator_->Show(SidePanelEntry::Id::kBookmarks);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kBookmarks);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
auto* bookmarks_entry = coordinator_->GetCurrentSidePanelEntryForTesting();
// Switch to a contextual entry and verify the active entry is updated.
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
auto* side_search_entry = coordinator_->GetCurrentSidePanelEntryForTesting();
// Switch to a tab where this contextual entry is not available and verify we
// fall back to the last seen global entry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
EXPECT_EQ(bookmarks_entry,
coordinator_->GetCurrentSidePanelEntryForTesting());
// Switch back to the tab where the contextual entry was visible and verify it
// is shown.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
EXPECT_EQ(side_search_entry,
coordinator_->GetCurrentSidePanelEntryForTesting());
}
TEST_F(SidePanelCoordinatorTest, TogglePanelWithContextualEntryShowing) {
// Open side panel and verify it opens to kReadingList by default.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Toggle();
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to a different global entry and verify the active entry is updated.
coordinator_->Show(SidePanelEntry::Id::kBookmarks);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kBookmarks);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to a contextual entry and verify the active entry is updated.
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Close the side panel and verify the contextual registry's last active entry
// is reset.
coordinator_->Toggle();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(GetLastActiveGlobalEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
EXPECT_FALSE(global_registry_->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Reopen the side panel and verify it reopens to the last active global
// entry.
coordinator_->Toggle();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kBookmarks);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kBookmarks);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
}
TEST_F(SidePanelCoordinatorTest,
SwitchBetweenTabWithContextualEntryAndTabWithNoEntry) {
// Open side panel to contextual entry and verify.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to another tab and verify the side panel is closed.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_FALSE(GetLastActiveEntryKey().has_value());
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch back to the tab with the contextual entry open and verify the side
// panel is then open.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
}
TEST_F(
SidePanelCoordinatorTest,
SwitchBetweenTabWithContextualEntryAndTabWithNoEntryWhenThereIsALastActiveGlobalEntry) {
coordinator_->Toggle();
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
coordinator_->Toggle();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(GetLastActiveGlobalEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(global_registry_->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Open side panel to contextual entry and verify.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to another tab and verify the side panel is closed.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch back to the tab with the contextual entry open and verify the side
// panel is then open.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
}
TEST_F(SidePanelCoordinatorTest,
SwitchBackToTabWithPreviouslyVisibleContextualEntry) {
// Open side panel to contextual entry and verify.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to a global entry and verify the contextual entry is no longer
// active.
coordinator_->Show(SidePanelEntry::Id::kReadingList);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to a different tab and verify state.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch back to the original tab and verify the contextual entry is not
// active or showing.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
}
TEST_F(SidePanelCoordinatorTest,
SwitchBackToTabWithContextualEntryAfterClosingGlobal) {
// Open side panel to contextual entry and verify.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
coordinator_->Show(SidePanelEntry::Id::kSideSearch);
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Switch to another tab and verify the side panel is closed.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_FALSE(GetLastActiveEntryKey().has_value());
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Open a global entry and verify.
coordinator_->Show(SidePanelEntry::Id::kReadingList);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(global_registry_->active_entry(),
SidePanelEntry::Id::kReadingList);
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Verify the panel closes but the first tab still has an active entry.
coordinator_->Toggle();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kReadingList);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
// Verify returning to the first tab reopens the side panel to the active
// contextual entry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(global_registry_->active_entry().has_value());
VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
SidePanelEntry::Id::kSideSearch);
EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
}
class TestSidePanelObserver : public SidePanelEntryObserver {
public:
explicit TestSidePanelObserver(SidePanelRegistry* registry)
: registry_(registry) {}
~TestSidePanelObserver() override = default;
void OnEntryHidden(SidePanelEntry* entry) override {
registry_->Deregister(entry->key());
}
private:
raw_ptr<SidePanelRegistry> registry_;
};
TEST_F(SidePanelCoordinatorTest,
EntryRegistersOnBeingHiddenFromSwitchToOtherEntry) {
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
// Create an observer that deregisters the entry once it is hidden.
auto observer =
std::make_unique<TestSidePanelObserver>(contextual_registries_[0]);
auto entry = std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kAssistant, u"Assistant",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() { return std::make_unique<views::View>(); }));
entry->AddObserver(observer.get());
contextual_registries_[0]->Register(std::move(entry));
coordinator_->Show(SidePanelEntry::Id::kAssistant);
// Switch to another entry.
coordinator_->Show(SidePanelEntry::Id::kReadingList);
// Verify that the previous entry has deregistered.
EXPECT_FALSE(contextual_registries_[0]->GetEntryForKey(
SidePanelEntry::Key(SidePanelEntry::Id::kAssistant)));
}
TEST_F(SidePanelCoordinatorTest,
EntryRegistersOnBeingHiddenFromSidePanelClose) {
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
// Create an observer that deregisters the entry once it is hidden.
auto observer =
std::make_unique<TestSidePanelObserver>(contextual_registries_[0]);
auto entry = std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kAssistant, u"Assistant",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() { return std::make_unique<views::View>(); }));
entry->AddObserver(observer.get());
contextual_registries_[0]->Register(std::move(entry));
coordinator_->Show(SidePanelEntry::Id::kAssistant);
// Close the sidepanel.
coordinator_->Toggle();
// Verify that the previous entry has deregistered.
EXPECT_FALSE(contextual_registries_[0]->GetEntryForKey(
SidePanelEntry::Key(SidePanelEntry::Id::kAssistant)));
}
TEST_F(SidePanelCoordinatorTest, ShouldNotRecreateTheSameEntry) {
// Switch to a tab without a contextual entry for lens, so that Show() shows
// the global entry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
int count = 0;
global_registry_->Register(std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kLens, u"lens",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating(
[](int* count) {
(*count)++;
return std::make_unique<views::View>();
},
&count)));
coordinator_->Show(SidePanelEntry::Id::kLens);
ASSERT_EQ(1, count);
coordinator_->Show(SidePanelEntry::Id::kLens);
ASSERT_EQ(1, count);
}
// closes side panel if the active entry is de-registered when open
TEST_F(SidePanelCoordinatorTest, GlobalEntryDeregisteredWhenVisible) {
coordinator_->Show(SidePanelEntry::Id::kBookmarks);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
global_registry_->Deregister(
SidePanelEntry::Key(SidePanelEntry::Id::kBookmarks));
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_FALSE(GetLastActiveEntryKey().has_value());
}
// resets last active entry id if active global entry de-registers when closed
TEST_F(SidePanelCoordinatorTest, GlobalEntryDeregisteredWhenClosed) {
coordinator_->Show(SidePanelEntry::Id::kBookmarks);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
coordinator_->Close();
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
global_registry_->Deregister(
SidePanelEntry::Key(SidePanelEntry::Id::kBookmarks));
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_FALSE(GetLastActiveEntryKey().has_value());
}
TEST_F(SidePanelCoordinatorTest, ComboboxAdditionsDoNotChangeSelection) {
SidePanelEntry::Id earlier_sorted_entry =
std::min(SidePanelEntry::Id::kSideSearch, SidePanelEntry::Id::kLens);
SidePanelEntry::Id later_sorted_entry =
std::max(SidePanelEntry::Id::kSideSearch, SidePanelEntry::Id::kLens);
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
content::WebContents* active_contents =
browser_view()->GetActiveWebContents();
auto* contextual_registry = SidePanelRegistry::Get(active_contents);
contextual_registry->Deregister(SidePanelEntry::Key(earlier_sorted_entry));
coordinator_->Show(later_sorted_entry);
// Verify the selected index in the combobox is the later entry.
absl::optional<size_t> selected_index =
coordinator_->GetComboboxForTesting()->GetSelectedIndex();
EXPECT_TRUE(selected_index.has_value());
EXPECT_EQ(coordinator_->GetComboboxModelForTesting()
->GetKeyAt(selected_index.value())
.id(),
later_sorted_entry);
// Add back the earlier entry and verify the selected index is still correct.
contextual_registry->Register(std::make_unique<SidePanelEntry>(
earlier_sorted_entry, u"testing1",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() { return std::make_unique<views::View>(); })));
EXPECT_EQ(coordinator_->GetCurrentSidePanelEntryForTesting()->key().id(),
later_sorted_entry);
selected_index = coordinator_->GetComboboxForTesting()->GetSelectedIndex();
EXPECT_TRUE(selected_index.has_value());
EXPECT_EQ(coordinator_->GetComboboxModelForTesting()
->GetKeyAt(selected_index.value())
.id(),
later_sorted_entry);
}
// Test that Show() shows the contextual extension entry if available for the
// current tab. Otherwise it shows the global extension entry. Note: only
// extensions will be able to have their entries exist in both the global and
// contextual registries.
TEST_F(SidePanelCoordinatorTest, ShowGlobalAndContextualExtensionEntries) {
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
SidePanelEntry::Key extension_key(SidePanelEntry::Id::kExtension,
"extension_id");
int global_count = 0;
int contextual_count = 0;
auto increment_count = [](int* count) {
(*count)++;
return std::make_unique<views::View>();
};
global_registry_->Register(std::make_unique<SidePanelEntry>(
extension_key, u"extension",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating(increment_count, &global_count)));
contextual_registries_[0]->Register(std::make_unique<SidePanelEntry>(
extension_key, u"extension",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating(increment_count, &contextual_count)));
coordinator_->Show(extension_key);
ASSERT_EQ(1, contextual_count);
ASSERT_EQ(0, global_count);
// Switch to a tab that does not have an extension entry registered for its
// contextual registry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
coordinator_->Show(extension_key);
ASSERT_EQ(1, contextual_count);
ASSERT_EQ(1, global_count);
}
// Test that the combobox shows the correct number of extension entries when
// global or contextual entries are registered.
TEST_F(SidePanelCoordinatorTest, RegisterExtensionEntries) {
// Make sure the second tab is active.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
SidePanelEntry::Key extension_1_key(SidePanelEntry::Id::kExtension,
"extension_1");
SidePanelEntry::Key extension_2_key(SidePanelEntry::Id::kExtension,
"extension_2");
auto* combobox_model = coordinator_->GetComboboxModelForTesting();
EXPECT_FALSE(combobox_model->HasKey(extension_1_key));
// Currently on the second tab. Sanity check that registering an entry on the
// first tab should not show an entry in the combobox.
contextual_registries_[0]->Register(CreateEntry(extension_1_key));
EXPECT_FALSE(combobox_model->HasKey(extension_1_key));
contextual_registries_[1]->Register(CreateEntry(extension_1_key));
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
// Check that registering a global entry while the combobox contains an item
// for the contextual entry still results in one item for an extension.
global_registry_->Register(CreateEntry(extension_1_key));
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
EXPECT_FALSE(combobox_model->HasKey(extension_2_key));
global_registry_->Register(CreateEntry(extension_2_key));
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_2_key));
// Check that registering an entry on the active tab while the combobox
// contains an item for the global entry still results in one item for an
// extension.
contextual_registries_[1]->Register(CreateEntry(extension_2_key));
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_2_key));
}
// Test that the combobox shows the correct number of extension entries when
// global or contextual entries are deregistered.
TEST_F(SidePanelCoordinatorTest, DeregisterExtensionEntries) {
// Make sure the second tab is active.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
SidePanelEntry::Key extension_1_key(SidePanelEntry::Id::kExtension,
"extension_1");
auto* combobox_model = coordinator_->GetComboboxModelForTesting();
// Registers an entry in the global and active contextual registry.
auto register_entries = [this, &combobox_model, &extension_1_key]() {
contextual_registries_[1]->Register(CreateEntry(extension_1_key));
global_registry_->Register(CreateEntry(extension_1_key));
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
};
register_entries();
// If the contextual entry is deregistered while there exists a global entry,
// an entry should still be shown in the combobox.
contextual_registries_[1]->Deregister(extension_1_key);
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
global_registry_->Deregister(extension_1_key);
EXPECT_FALSE(combobox_model->HasKey(extension_1_key));
register_entries();
// If the global entry is deregistered while there exists an active contextual
// entry, an entry should still be shown in the combobox.
global_registry_->Deregister(extension_1_key);
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
contextual_registries_[1]->Deregister(extension_1_key);
EXPECT_FALSE(combobox_model->HasKey(extension_1_key));
}
// Test that the combobox shows the correct number of extension entries in
// between tab switches.
TEST_F(SidePanelCoordinatorTest, ExtensionEntriesTabSwitching) {
// Make sure the second tab is active.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
SidePanelEntry::Key extension_1_key(SidePanelEntry::Id::kExtension,
"extension_1");
auto* combobox_model = coordinator_->GetComboboxModelForTesting();
contextual_registries_[1]->Register(CreateEntry(extension_1_key));
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
// Switch to the first tab, which does not have an extension entry for its
// registry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_FALSE(combobox_model->HasKey(extension_1_key));
// Register an extension entry to the global registry.
global_registry_->Register(CreateEntry(extension_1_key));
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
// Switch back to the second tab. There should be only one extension entry in
// the combobox, corresponding to the contextual registry's extension entry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
// Finally, switch back to the first tab. There should be only one extension
// entry in the combobox, corresponding to the global registry's extension
// entry.
browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_EQ(1, combobox_model->GetKeyCountForTesting(extension_1_key));
}
// Test that the SidePanelCoordinator behaves and updates corrected when dealing
// with entries that load/display asynchronously.
class SidePanelCoordinatorLoadingContentTest : public SidePanelCoordinatorTest {
public:
void SetUp() override {
TestWithBrowserView::SetUp();
AddTab(browser_view()->browser(), GURL("http://foo1.com"));
AddTab(browser_view()->browser(), GURL("http://foo2.com"));
coordinator_ = browser_view()->side_panel_coordinator();
global_registry_ = coordinator_->GetGlobalSidePanelRegistry();
// Add a kSideSearch entry to the global registry with loading content not
// available.
std::unique_ptr<SidePanelEntry> entry1 = std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kSideSearch, u"testing1",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() {
auto view = std::make_unique<views::View>();
SidePanelUtil::GetSidePanelContentProxy(view.get())
->SetAvailable(false);
return view;
}));
loading_content_entry1_ = entry1.get();
global_registry_->Register(std::move(entry1));
// Add a kLens entry to the global registry with loading content not
// available.
std::unique_ptr<SidePanelEntry> entry2 = std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kLens, u"testing2",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() {
auto view = std::make_unique<views::View>();
SidePanelUtil::GetSidePanelContentProxy(view.get())
->SetAvailable(false);
return view;
}));
loading_content_entry2_ = entry2.get();
global_registry_->Register(std::move(entry2));
// Add a kAssistant entry to the global registry with content available.
std::unique_ptr<SidePanelEntry> entry3 = std::make_unique<SidePanelEntry>(
SidePanelEntry::Id::kAssistant, u"testing3",
ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
base::BindRepeating([]() {
auto view = std::make_unique<views::View>();
SidePanelUtil::GetSidePanelContentProxy(view.get())
->SetAvailable(true);
return view;
}));
loaded_content_entry1_ = entry3.get();
global_registry_->Register(std::move(entry3));
}
raw_ptr<SidePanelEntry> loading_content_entry1_;
raw_ptr<SidePanelEntry> loading_content_entry2_;
raw_ptr<SidePanelEntry> loaded_content_entry1_;
};
TEST_F(SidePanelCoordinatorLoadingContentTest,
ContentAndComboboxDelayForLoadingContent) {
coordinator_->Show(loading_content_entry1_->key().id());
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
// A loading entry's view should be stored as the cached view and be
// unavailable.
views::View* loading_content = loading_content_entry1_->CachedView();
EXPECT_NE(loading_content, nullptr);
SidePanelContentProxy* loading_content_proxy =
SidePanelUtil::GetSidePanelContentProxy(loading_content);
EXPECT_FALSE(loading_content_proxy->IsAvailable());
// Set the content proxy to available.
loading_content_proxy->SetAvailable(true);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
// Switch to another entry that has loading content.
coordinator_->Show(loading_content_entry2_->key().id());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
loading_content_entry1_->key().id());
loading_content = loading_content_entry2_->CachedView();
EXPECT_NE(loading_content, nullptr);
loading_content_proxy =
SidePanelUtil::GetSidePanelContentProxy(loading_content);
EXPECT_FALSE(loading_content_proxy->IsAvailable());
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loading_content_entry1_->key().id());
// Set as available and make sure the combobox has updated.
loading_content_proxy->SetAvailable(true);
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loading_content_entry2_->key().id());
}
TEST_F(SidePanelCoordinatorLoadingContentTest,
TriggerSwitchToNewEntryDuringContentLoad) {
coordinator_->Show(loaded_content_entry1_->key().id());
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loaded_content_entry1_->key().id());
// Switch to loading_content_entry1_ that has loading content.
coordinator_->Show(loading_content_entry1_->key().id());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
loaded_content_entry1_->key().id());
views::View* loading_content1 = loading_content_entry1_->CachedView();
EXPECT_NE(loading_content1, nullptr);
SidePanelContentProxy* loading_content_proxy1 =
SidePanelUtil::GetSidePanelContentProxy(loading_content1);
EXPECT_FALSE(loading_content_proxy1->IsAvailable());
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loaded_content_entry1_->key().id());
// Verify the loading_content_entry1_ is the loading entry.
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry1_);
// While that entry is loading, switch to a different entry with content that
// needs to load.
coordinator_->Show(loading_content_entry2_->key().id());
views::View* loading_content2 = loading_content_entry2_->CachedView();
EXPECT_NE(loading_content2, nullptr);
SidePanelContentProxy* loading_content_proxy2 =
SidePanelUtil::GetSidePanelContentProxy(loading_content2);
EXPECT_FALSE(loading_content_proxy2->IsAvailable());
// Verify the loading_content_entry2_ is no longer the loading entry.
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry2_);
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loaded_content_entry1_->key().id());
// Set loading_content_entry1_ as available and verify it is not made the
// active entry.
loading_content_proxy1->SetAvailable(true);
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry2_);
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loaded_content_entry1_->key().id());
// Set loading_content_entry2_ as available and verify it is made the active
// entry.
loading_content_proxy2->SetAvailable(true);
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), nullptr);
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loading_content_entry2_->key().id());
}
TEST_F(SidePanelCoordinatorLoadingContentTest,
TriggerSwitchToCurrentVisibleEntryDuringContentLoad) {
coordinator_->Show(loading_content_entry1_->key().id());
EXPECT_FALSE(browser_view()->unified_side_panel()->GetVisible());
// A loading entry's view should be stored as the cached view and be
// unavailable.
views::View* loading_content = loading_content_entry1_->CachedView();
EXPECT_NE(loading_content, nullptr);
SidePanelContentProxy* loading_content_proxy1 =
SidePanelUtil::GetSidePanelContentProxy(loading_content);
EXPECT_FALSE(loading_content_proxy1->IsAvailable());
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry1_);
// Set the content proxy to available.
loading_content_proxy1->SetAvailable(true);
EXPECT_TRUE(browser_view()->unified_side_panel()->GetVisible());
// Switch to loading_content_entry2_ that has loading content.
coordinator_->Show(loading_content_entry2_->key().id());
EXPECT_TRUE(GetLastActiveEntryKey().has_value());
EXPECT_EQ(GetLastActiveEntryKey().value().id(),
loading_content_entry1_->key().id());
loading_content = loading_content_entry2_->CachedView();
EXPECT_NE(loading_content, nullptr);
SidePanelContentProxy* loading_content_proxy2 =
SidePanelUtil::GetSidePanelContentProxy(loading_content);
EXPECT_FALSE(loading_content_proxy2->IsAvailable());
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loading_content_entry1_->key().id());
// Verify the loading_content_entry2_ is the loading entry.
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry2_);
// While that entry is loading, switch back to the currently showing entry.
coordinator_->Show(loading_content_entry1_->key().id());
// Verify the loading_content_entry2_ is no longer the loading entry.
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), nullptr);
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loading_content_entry1_->key().id());
// Set loading_content_entry2_ as available and verify it is not made the
// active entry.
loading_content_proxy2->SetAvailable(true);
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loading_content_entry1_->key().id());
// Show loading_content_entry2_ and verify it shows without availability
// needing to be set again.
coordinator_->Show(loading_content_entry2_->key().id());
EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), nullptr);
EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
loading_content_entry2_->key().id());
}