blob: 947c66ba2e041d54b6bc6e2157a06860ec06a805 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/run_until.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.h"
#include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h"
#include "chrome/browser/ui/views/tabs/vertical/vertical_tab_group_header_view.h"
#include "chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view.h"
#include "chrome/browser/ui/views/test/vertical_tabs_interactive_test_mixin.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/controls/button/label_button.h"
class TabCollectionNodeInteractiveUiTest
: public VerticalTabsInteractiveTestMixin<InteractiveBrowserTest> {
public:
TabCollectionNodeInteractiveUiTest() = default;
~TabCollectionNodeInteractiveUiTest() override = default;
protected:
RootTabCollectionNode* GetRootNode() {
return browser()
->GetBrowserView()
.vertical_tab_strip_region_view_for_testing()
->root_node_for_testing();
}
views::FocusManager* GetFocusManager() {
return browser()->GetBrowserView().GetFocusManager();
}
};
IN_PROC_BROWSER_TEST_F(TabCollectionNodeInteractiveUiTest,
ValidateViewFocusOrder) {
// Initial Order: [A, B, C, D, E, F].
for (size_t i = 0; i < 5; i++) {
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url::kAboutBlankURL),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
}
// Final Order: [D, E] [[A, B, C], F].
auto group_id = browser()->tab_strip_model()->AddToNewGroup({1, 2});
browser()->tab_strip_model()->SetTabPinned(3, true);
browser()->tab_strip_model()->SetTabPinned(4, true);
browser()->tab_strip_model()->AddToExistingGroup({2}, group_id);
browser()->tab_strip_model()->ActivateTabAt(0);
const auto& pinned_node = GetRootNode()->children()[0];
EXPECT_EQ(pinned_node->type(), TabCollectionNode::Type::PINNED);
const auto& unpinned_node = GetRootNode()->children()[1];
EXPECT_EQ(unpinned_node->type(), TabCollectionNode::Type::UNPINNED);
const auto& group_node = unpinned_node->children()[0];
EXPECT_EQ(group_node->type(), TabCollectionNode::Type::GROUP);
ASSERT_TRUE(base::test::RunUntil(
[&]() { return pinned_node->children().size() == 2u; }));
ASSERT_TRUE(base::test::RunUntil(
[&]() { return unpinned_node->children().size() == 2u; }));
auto* group_view =
views::AsViewClass<VerticalTabGroupView>(group_node->view());
CHECK(group_view);
group_view->group_header()->editor_bubble_button()->SetVisible(true);
// Focus Order: D, E, A, B, C, F.
const std::vector<views::View*> views_focus_order = {
pinned_node->children()[0]->view(),
pinned_node->children()[1]->view(),
group_view->group_header(),
group_view->group_header()->editor_bubble_button(),
group_node->children()[0]->view(),
group_node->children()[1]->view(),
group_node->children()[2]->view(),
unpinned_node->children()[1]->view()};
// Assert focus order.
GetFocusManager()->SetKeyboardAccessible(true);
for (size_t i = 0; i < views_focus_order.size() - 1; ++i) {
views::View* next = GetFocusManager()->GetNextFocusableView(
views_focus_order[i], nullptr, false, true);
EXPECT_EQ(next, views_focus_order[i + 1]);
}
}
IN_PROC_BROWSER_TEST_F(TabCollectionNodeInteractiveUiTest,
KeepsFocusWhenMovedOutOfGroup) {
constexpr char kTabNodeViewName[] = "TabNodeView";
RunTestSequence(
// Setup the group.
Do([this]() {
GetFocusManager()->SetKeyboardAccessible(true);
browser()->tab_strip_model()->AddToNewGroup({0});
RunScheduledLayouts();
}),
NameView(kTabNodeViewName,
base::BindLambdaForTesting([this]() -> views::View* {
TabCollectionNode* group_node =
unpinned_collection_node()->GetChildNodeOfType(
TabCollectionNode::Type::GROUP);
return group_node->children()[0]->view();
})),
// Request Focus on Tab.
WithView(kTabNodeViewName,
[this](views::View* view) {
EXPECT_TRUE(
ui_test_utils::BringBrowserWindowToFront(browser()));
view->RequestFocus();
}),
// Wait for Focus on Tab.
CheckViewProperty(kTabNodeViewName, &views::View::HasFocus, true),
// Remove the tab from the group.
Do([this]() {
browser()->tab_strip_model()->RemoveFromGroup({0});
RunScheduledLayouts();
}),
// Verify focus is maintained after reparenting.
CheckResult(base::BindLambdaForTesting([this]() {
// After being removed from the group, the tab is back in
// the unpinned collection.
return unpinned_collection_node()
->children()[0]
->view()
->HasFocus();
}),
true));
}