| // Copyright 2012 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/tabs/tab_strip_model.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h" |
| #include "chrome/browser/ui/tabs/features.h" |
| #include "chrome/browser/ui/tabs/split_tab_metrics.h" |
| #include "chrome/browser/ui/tabs/tab_enums.h" |
| #include "chrome/browser/ui/tabs/tab_group_model.h" |
| #include "chrome/browser/ui/tabs/tab_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model_test_utils.h" |
| #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h" |
| #include "chrome/browser/ui/tabs/tab_utils.h" |
| #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/test/base/browser_with_test_window_test.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/commerce/core/commerce_utils.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/tab_groups/tab_group_color.h" |
| #include "components/tab_groups/tab_group_id.h" |
| #include "components/tabs/public/split_tab_data.h" |
| #include "components/tabs/public/split_tab_visual_data.h" |
| #include "components/tabs/public/tab_group.h" |
| #include "components/tabs/public/tab_group_tab_collection.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "components/web_modal/web_contents_modal_dialog_manager.h" |
| #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/models/list_selection_model.h" |
| |
| using content::WebContents; |
| |
| namespace { |
| |
| struct ObservedSelectionChange { |
| ObservedSelectionChange() = default; |
| ObservedSelectionChange(const ObservedSelectionChange& other) = default; |
| ~ObservedSelectionChange() = default; |
| |
| ObservedSelectionChange& operator=(const ObservedSelectionChange& other) = |
| default; |
| |
| explicit ObservedSelectionChange( |
| const TabStripSelectionChange& selection_change) { |
| old_tab = selection_change.old_tab ? selection_change.old_tab->GetHandle() |
| : tabs::TabHandle::Null(); |
| new_tab = selection_change.new_tab ? selection_change.new_tab->GetHandle() |
| : tabs::TabHandle::Null(); |
| |
| old_model = selection_change.old_model; |
| new_model = selection_change.new_model; |
| } |
| |
| tabs::TabHandle old_tab = tabs::TabHandle::Null(); |
| tabs::TabHandle new_tab = tabs::TabHandle::Null(); |
| |
| ui::ListSelectionModel old_model; |
| ui::ListSelectionModel new_model; |
| }; |
| |
| class MockTabStripModelObserver : public TabStripModelObserver { |
| public: |
| MockTabStripModelObserver() = default; |
| MockTabStripModelObserver(const MockTabStripModelObserver&) = delete; |
| MockTabStripModelObserver& operator=(const MockTabStripModelObserver&) = |
| delete; |
| ~MockTabStripModelObserver() override = default; |
| |
| enum TabStripModelObserverAction { |
| INSERT, |
| CLOSE, |
| DETACH, |
| ACTIVATE, |
| DEACTIVATE, |
| SELECT, |
| MOVE, |
| CHANGE, |
| PINNED, |
| REPLACED, |
| CLOSE_ALL, |
| CLOSE_ALL_CANCELED, |
| CLOSE_ALL_COMPLETED, |
| GROUP_CHANGED, |
| }; |
| |
| struct State { |
| State(WebContents* dst_contents, |
| std::optional<size_t> dst_index, |
| TabStripModelObserverAction action) |
| : dst_contents(dst_contents), dst_index(dst_index), action(action) {} |
| |
| raw_ptr<WebContents, DanglingUntriaged> src_contents = nullptr; |
| raw_ptr<WebContents, DanglingUntriaged> dst_contents; |
| std::optional<size_t> src_index; |
| std::optional<size_t> dst_index; |
| int change_reason = CHANGE_REASON_NONE; |
| bool foreground = false; |
| TabStripModelObserverAction action; |
| |
| std::string ToString() const { |
| std::ostringstream oss; |
| const auto optional_to_string = [](const auto& opt) { |
| return opt.has_value() ? base::NumberToString(opt.value()) |
| : std::string("<none>"); |
| }; |
| oss << "State change: " << StringifyActionName(action) |
| << "\n Source index: " << optional_to_string(src_index) |
| << "\n Destination index: " << optional_to_string(dst_index) |
| << "\n Source contents: " << src_contents |
| << "\n Destination contents: " << dst_contents |
| << "\n Change reason: " << change_reason |
| << "\n Foreground: " << (foreground ? "yes" : "no"); |
| return oss.str(); |
| } |
| |
| bool operator==(const State& state) const { |
| return src_contents == state.src_contents && |
| dst_contents == state.dst_contents && |
| src_index == state.src_index && dst_index == state.dst_index && |
| change_reason == state.change_reason && |
| foreground == state.foreground && action == state.action; |
| } |
| }; |
| |
| int GetStateCount() const { return static_cast<int>(states_.size()); } |
| |
| // Returns (by way of parameters) the number of state's with CLOSE_ALL, |
| // CLOSE_ALL_CANCELED and CLOSE_ALL_COMPLETED. |
| void GetCloseCounts(int* close_all_count, |
| int* close_all_canceled_count, |
| int* close_all_completed_count) { |
| *close_all_count = *close_all_canceled_count = *close_all_completed_count = |
| 0; |
| for (int i = 0; i < GetStateCount(); ++i) { |
| switch (GetStateAt(i).action) { |
| case CLOSE_ALL: |
| (*close_all_count)++; |
| break; |
| case CLOSE_ALL_CANCELED: |
| (*close_all_canceled_count)++; |
| break; |
| case CLOSE_ALL_COMPLETED: |
| (*close_all_completed_count)++; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| const State& GetStateAt(int index) const { |
| DCHECK(index >= 0 && index < GetStateCount()); |
| return states_[index]; |
| } |
| |
| void ExpectStateEquals(int index, const State& state) { |
| EXPECT_TRUE(GetStateAt(index) == state) |
| << "Got " << GetStateAt(index).ToString() << "\nExpected " |
| << state.ToString(); |
| } |
| |
| void PushInsertState(WebContents* contents, int index, bool foreground) { |
| State s(contents, index, INSERT); |
| s.foreground = foreground; |
| states_.push_back(s); |
| } |
| |
| void PushActivateState(WebContents* old_contents, |
| WebContents* new_contents, |
| std::optional<size_t> index, |
| int reason) { |
| State s(new_contents, index, ACTIVATE); |
| s.src_contents = old_contents; |
| s.change_reason = reason; |
| states_.push_back(s); |
| } |
| |
| void PushDeactivateState(WebContents* contents, |
| const ui::ListSelectionModel& old_model) { |
| states_.emplace_back(contents, old_model.active(), DEACTIVATE); |
| } |
| |
| void PushSelectState(content::WebContents* new_contents, |
| const ui::ListSelectionModel& old_model, |
| const ui::ListSelectionModel& new_model) { |
| State s(new_contents, new_model.active(), SELECT); |
| s.src_index = old_model.active(); |
| states_.push_back(s); |
| } |
| |
| void PushMoveState(WebContents* contents, int from_index, int to_index) { |
| const auto tab_index_to_selection_model_index = |
| [](int tab_index) -> std::optional<size_t> { |
| if (tab_index == TabStripModel::kNoTab) { |
| return std::nullopt; |
| } |
| DCHECK_GE(tab_index, 0); |
| return static_cast<size_t>(tab_index); |
| }; |
| State s(contents, tab_index_to_selection_model_index(to_index), MOVE); |
| s.src_index = tab_index_to_selection_model_index(from_index); |
| states_.push_back(s); |
| } |
| |
| void PushCloseState(WebContents* contents, int index) { |
| states_.emplace_back(contents, index, CLOSE); |
| } |
| |
| void PushDetachState(WebContents* contents, int index, bool was_active) { |
| states_.emplace_back(contents, index, DETACH); |
| } |
| |
| void PushReplaceState(WebContents* old_contents, |
| WebContents* new_contents, |
| int index) { |
| State s(new_contents, index, REPLACED); |
| s.src_contents = old_contents; |
| states_.push_back(s); |
| } |
| |
| struct TabGroupUpdate { |
| int contents_update_count = 0; |
| int visuals_update_count = 0; |
| }; |
| |
| const std::map<tab_groups::TabGroupId, TabGroupUpdate>& group_updates() |
| const { |
| return group_updates_; |
| } |
| |
| const TabGroupUpdate group_update(tab_groups::TabGroupId group) { |
| return group_updates_[group]; |
| } |
| |
| // TabStripModelObserver overrides: |
| void OnTabStripModelChanged( |
| TabStripModel* tab_strip_model, |
| const TabStripModelChange& change, |
| const TabStripSelectionChange& selection) override { |
| latest_selection_change_ = ObservedSelectionChange(selection); |
| switch (change.type()) { |
| case TabStripModelChange::kInserted: { |
| for (const auto& contents : change.GetInsert()->contents) { |
| PushInsertState(contents.contents, contents.index, |
| selection.new_contents == contents.contents); |
| } |
| break; |
| } |
| case TabStripModelChange::kRemoved: { |
| for (const auto& contents : change.GetRemove()->contents) { |
| switch (contents.remove_reason) { |
| case TabStripModelChange::RemoveReason::kDeleted: |
| PushCloseState(contents.contents, contents.index); |
| break; |
| case TabStripModelChange::RemoveReason::kInsertedIntoOtherTabStrip: |
| break; |
| } |
| PushDetachState(contents.contents, contents.index, |
| selection.old_contents == contents.contents); |
| } |
| break; |
| } |
| case TabStripModelChange::kReplaced: { |
| auto* replace = change.GetReplace(); |
| PushReplaceState(replace->old_contents, replace->new_contents, |
| replace->index); |
| break; |
| } |
| case TabStripModelChange::kMoved: { |
| auto* move = change.GetMove(); |
| PushMoveState(move->contents, move->from_index, move->to_index); |
| break; |
| } |
| case TabStripModelChange::kSelectionOnly: |
| break; |
| } |
| |
| if (selection.active_tab_changed()) { |
| if (selection.old_contents && selection.selection_changed()) { |
| PushDeactivateState(selection.old_contents, selection.old_model); |
| } |
| |
| PushActivateState(selection.old_contents, selection.new_contents, |
| selection.new_model.active(), selection.reason); |
| } |
| |
| if (selection.selection_changed()) { |
| PushSelectState(selection.new_contents, selection.old_model, |
| selection.new_model); |
| } |
| } |
| |
| void TabGroupedStateChanged(TabStripModel* tab_strip_model, |
| std::optional<tab_groups::TabGroupId> old_group, |
| std::optional<tab_groups::TabGroupId> new_group, |
| tabs::TabInterface* tab, |
| int index) override { |
| if (old_group.has_value()) { |
| group_updates_[old_group.value()].contents_update_count++; |
| } |
| |
| if (new_group.has_value()) { |
| group_updates_[new_group.value()].contents_update_count++; |
| } |
| } |
| |
| void OnTabGroupChanged(const TabGroupChange& change) override { |
| switch (change.type) { |
| case TabGroupChange::kCreated: { |
| group_updates_[change.group] = TabGroupUpdate(); |
| break; |
| } |
| case TabGroupChange::kEditorOpened: { |
| break; |
| } |
| case TabGroupChange::kVisualsChanged: { |
| group_updates_[change.group].visuals_update_count++; |
| break; |
| } |
| case TabGroupChange::kMoved: { |
| break; |
| } |
| case TabGroupChange::kClosed: { |
| group_updates_.erase(change.group); |
| break; |
| } |
| } |
| } |
| |
| ObservedSelectionChange GetLatestSelectionChange() { |
| return latest_selection_change_; |
| } |
| |
| void TabChangedAt(WebContents* contents, |
| int index, |
| TabChangeType change_type) override { |
| states_.emplace_back(contents, index, CHANGE); |
| } |
| |
| void TabPinnedStateChanged(TabStripModel* tab_strip_model, |
| WebContents* contents, |
| int index) override { |
| states_.emplace_back(contents, index, PINNED); |
| } |
| |
| void WillCloseAllTabs(TabStripModel* tab_strip_model) override { |
| states_.emplace_back(nullptr, std::nullopt, CLOSE_ALL); |
| } |
| |
| void CloseAllTabsStopped(TabStripModel* tab_strip_model, |
| CloseAllStoppedReason reason) override { |
| if (reason == kCloseAllCanceled) { |
| states_.emplace_back(nullptr, std::nullopt, CLOSE_ALL_CANCELED); |
| } else if (reason == kCloseAllCompleted) { |
| states_.emplace_back(nullptr, std::nullopt, CLOSE_ALL_COMPLETED); |
| } |
| } |
| |
| void ClearStates() { |
| states_.clear(); |
| group_updates_.clear(); |
| } |
| |
| private: |
| static std::string_view StringifyActionName( |
| TabStripModelObserverAction action) { |
| using enum TabStripModelObserverAction; |
| constexpr auto kObserverActionNames = |
| base::MakeFixedFlatMap<TabStripModelObserverAction, std::string_view>({ |
| {INSERT, "INSERT"}, |
| {CLOSE, "CLOSE"}, |
| {DETACH, "DETACH"}, |
| {ACTIVATE, "ACTIVATE"}, |
| {DEACTIVATE, "DEACTIVATE"}, |
| {SELECT, "SELECT"}, |
| {MOVE, "MOVE"}, |
| {CHANGE, "CHANGE"}, |
| {PINNED, "PINNED"}, |
| {REPLACED, "REPLACED"}, |
| {CLOSE_ALL, "CLOSE_ALL"}, |
| {CLOSE_ALL_CANCELED, "CLOSE_ALL_CANCELED"}, |
| {CLOSE_ALL_COMPLETED, "CLOSE_ALL_COMPLETED"}, |
| {GROUP_CHANGED, "GROUP_CHANGED"}, |
| }); |
| const auto iter = kObserverActionNames.find(action); |
| if (iter != kObserverActionNames.end()) { |
| return iter->second; |
| } |
| |
| NOTREACHED() << "bogus `TabStripModelObserverAction`: " << action; |
| } |
| |
| std::vector<State> states_; |
| ObservedSelectionChange latest_selection_change_; |
| std::map<tab_groups::TabGroupId, TabGroupUpdate> group_updates_; |
| }; |
| |
| } // namespace |
| |
| class TabStripModelTest : public testing::Test { |
| public: |
| TabStripModelTest() : profile_(new TestingProfile) {} |
| TabStripModelTest(const TabStripModelTest&) = delete; |
| TabStripModelTest& operator=(const TabStripModelTest&) = delete; |
| |
| void SetUp() override { |
| tabstrip_ = std::make_unique<TabStripModel>(delegate(), profile()); |
| scoped_feature_list_.InitWithFeatures( |
| {features::kSideBySide, features::kSideBySideSessionRestore}, {}); |
| tabstrip()->AddObserver(observer()); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| TabStripModel* tabstrip() { return tabstrip_.get(); } |
| |
| TestingProfile* profile() { return profile_.get(); } |
| |
| TestTabStripModelDelegate* delegate() { return &delegate_; } |
| |
| MockTabStripModelObserver* observer() { return &observer_; } |
| |
| base::test::ScopedFeatureList* scoped_feature_list() { |
| return &scoped_feature_list_; |
| } |
| |
| std::unique_ptr<WebContents> CreateWebContents() { |
| return content::WebContentsTester::CreateTestWebContents(profile(), |
| nullptr); |
| } |
| |
| std::unique_ptr<WebContents> CreateWebContentsWithSharedRPH( |
| WebContents* web_contents) { |
| WebContents::CreateParams create_params( |
| profile(), web_contents->GetPrimaryMainFrame()->GetSiteInstance()); |
| std::unique_ptr<WebContents> retval = WebContents::Create(create_params); |
| EXPECT_EQ(retval->GetPrimaryMainFrame()->GetProcess(), |
| web_contents->GetPrimaryMainFrame()->GetProcess()); |
| return retval; |
| } |
| |
| std::unique_ptr<WebContents> CreateWebContentsWithID(int id) { |
| std::unique_ptr<WebContents> contents = CreateWebContents(); |
| SetID(contents.get(), id); |
| return contents; |
| } |
| |
| void PrepareTabs(TabStripModel* model, int tab_count) { |
| for (int i = 0; i < tab_count; ++i) { |
| model->AppendWebContents(CreateWebContentsWithID(i), true); |
| } |
| } |
| |
| void PrepareTabstripForSelectionTest(TabStripModel* model, |
| int tab_count, |
| int pinned_count, |
| const std::vector<int> selected_tabs) { |
| ::PrepareTabstripForSelectionTest( |
| base::BindOnce(&TabStripModelTest::PrepareTabs, base::Unretained(this), |
| model), |
| model, tab_count, pinned_count, selected_tabs); |
| } |
| |
| void ExpectSelectionIsExactly(TabStripModel* tabstrip, |
| std::vector<int> selected) { |
| for (const int i : selected) { |
| EXPECT_TRUE(tabstrip_->selection_model().IsSelected(i)); |
| } |
| EXPECT_EQ(tabstrip_->selection_model().size(), selected.size()); |
| } |
| |
| bool HasTabSwitchStartTimeAtIndex(int index) { |
| return !content::WebContentsTester::For(tabstrip()->GetWebContentsAt(index)) |
| ->GetTabSwitchStartTime() |
| .is_null(); |
| } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| content::RenderViewHostTestEnabler rvh_test_enabler_; |
| const std::unique_ptr<TestingProfile> profile_; |
| const tabs::TabModel::PreventFeatureInitializationForTesting prevent_; |
| TestTabStripModelDelegate delegate_; |
| MockTabStripModelObserver observer_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| std::unique_ptr<TabStripModel> tabstrip_ = nullptr; |
| }; |
| |
| TEST_F(TabStripModelTest, TestBasicAPI) { |
| typedef MockTabStripModelObserver::State State; |
| |
| std::unique_ptr<WebContents> contents1 = CreateWebContentsWithID(1); |
| WebContents* raw_contents1 = contents1.get(); |
| |
| // Note! The ordering of these tests is important, each subsequent test |
| // builds on the state established in the previous. This is important if you |
| // ever insert tests rather than append. |
| |
| // Test AppendWebContents, ContainsIndex |
| { |
| EXPECT_FALSE(tabstrip()->ContainsIndex(0)); |
| tabstrip()->AppendWebContents(std::move(contents1), true); |
| EXPECT_TRUE(tabstrip()->ContainsIndex(0)); |
| EXPECT_EQ(1, tabstrip()->count()); |
| EXPECT_EQ(3, observer()->GetStateCount()); |
| State s1(raw_contents1, 0, MockTabStripModelObserver::INSERT); |
| s1.foreground = true; |
| observer()->ExpectStateEquals(0, s1); |
| State s2(raw_contents1, 0, MockTabStripModelObserver::ACTIVATE); |
| observer()->ExpectStateEquals(1, s2); |
| State s3(raw_contents1, 0, MockTabStripModelObserver::SELECT); |
| s3.src_index = std::nullopt; |
| observer()->ExpectStateEquals(2, s3); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("1", GetTabStripStateString(tabstrip())); |
| |
| // Test InsertWebContentsAt, foreground tab. |
| std::unique_ptr<WebContents> contents2 = CreateWebContentsWithID(2); |
| content::WebContents* raw_contents2 = contents2.get(); |
| { |
| tabstrip()->InsertWebContentsAt(1, std::move(contents2), |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(2, tabstrip()->count()); |
| EXPECT_EQ(4, observer()->GetStateCount()); |
| State s1(raw_contents2, 1, MockTabStripModelObserver::INSERT); |
| s1.foreground = true; |
| observer()->ExpectStateEquals(0, s1); |
| State s2(raw_contents1, 0, MockTabStripModelObserver::DEACTIVATE); |
| observer()->ExpectStateEquals(1, s2); |
| State s3(raw_contents2, 1, MockTabStripModelObserver::ACTIVATE); |
| s3.src_contents = raw_contents1; |
| observer()->ExpectStateEquals(2, s3); |
| State s4(raw_contents2, 1, MockTabStripModelObserver::SELECT); |
| s4.src_index = 0; |
| observer()->ExpectStateEquals(3, s4); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("1 2", GetTabStripStateString(tabstrip())); |
| |
| // Test InsertWebContentsAt, background tab. |
| std::unique_ptr<WebContents> contents3 = CreateWebContentsWithID(3); |
| WebContents* raw_contents3 = contents3.get(); |
| { |
| tabstrip()->InsertWebContentsAt(2, std::move(contents3), |
| AddTabTypes::ADD_NONE); |
| |
| EXPECT_EQ(3, tabstrip()->count()); |
| EXPECT_EQ(1, observer()->GetStateCount()); |
| State s1(raw_contents3, 2, MockTabStripModelObserver::INSERT); |
| s1.foreground = false; |
| observer()->ExpectStateEquals(0, s1); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("1 2 3", GetTabStripStateString(tabstrip())); |
| |
| // Test ActivateTabAt |
| { |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(3, observer()->GetStateCount()); |
| State s1(raw_contents2, 1, MockTabStripModelObserver::DEACTIVATE); |
| observer()->ExpectStateEquals(0, s1); |
| State s2(raw_contents3, 2, MockTabStripModelObserver::ACTIVATE); |
| s2.src_contents = raw_contents2; |
| s2.change_reason = TabStripModelObserver::CHANGE_REASON_USER_GESTURE; |
| observer()->ExpectStateEquals(1, s2); |
| State s3(raw_contents3, 2, MockTabStripModelObserver::SELECT); |
| s3.src_index = 1; |
| observer()->ExpectStateEquals(2, s3); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("1 2 3", GetTabStripStateString(tabstrip())); |
| |
| // Test DetachAndDeleteWebContentsAt |
| { |
| // add a background tab to detach |
| std::unique_ptr<WebContents> contents4 = CreateWebContentsWithID(4); |
| WebContents* raw_contents4 = contents4.get(); |
| tabstrip()->InsertWebContentsAt(3, std::move(contents4), |
| AddTabTypes::ADD_NONE); |
| // detach and delete the tab |
| tabstrip()->DetachAndDeleteWebContentsAt(3); |
| |
| EXPECT_EQ(3, observer()->GetStateCount()); |
| State s1(raw_contents4, 3, MockTabStripModelObserver::INSERT); |
| observer()->ExpectStateEquals(0, s1); |
| State s2(raw_contents4, 3, MockTabStripModelObserver::CLOSE); |
| observer()->ExpectStateEquals(1, s2); |
| State s3(raw_contents4, 3, MockTabStripModelObserver::DETACH); |
| observer()->ExpectStateEquals(2, s3); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("1 2 3", GetTabStripStateString(tabstrip())); |
| |
| // Test CloseWebContentsAt |
| { |
| int previous_tab_count = tabstrip()->count(); |
| tabstrip()->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(previous_tab_count - 1, tabstrip()->count()); |
| |
| EXPECT_EQ(5, observer()->GetStateCount()); |
| State s1(raw_contents3, 2, MockTabStripModelObserver::CLOSE); |
| observer()->ExpectStateEquals(0, s1); |
| State s2(raw_contents3, 2, MockTabStripModelObserver::DETACH); |
| observer()->ExpectStateEquals(1, s2); |
| State s3(raw_contents3, 2, MockTabStripModelObserver::DEACTIVATE); |
| observer()->ExpectStateEquals(2, s3); |
| State s4(raw_contents2, 1, MockTabStripModelObserver::ACTIVATE); |
| s4.src_contents = raw_contents3; |
| s4.change_reason = TabStripModelObserver::CHANGE_REASON_NONE; |
| observer()->ExpectStateEquals(3, s4); |
| State s5(raw_contents2, 1, MockTabStripModelObserver::SELECT); |
| s5.src_index = 2; |
| observer()->ExpectStateEquals(4, s5); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("1 2", GetTabStripStateString(tabstrip())); |
| |
| // Test MoveWebContentsAt, select_after_move == true |
| { |
| tabstrip()->MoveWebContentsAt(1, 0, true); |
| |
| EXPECT_EQ(2, observer()->GetStateCount()); |
| State s1(raw_contents2, 0, MockTabStripModelObserver::MOVE); |
| s1.src_index = 1; |
| observer()->ExpectStateEquals(0, s1); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("2 1", GetTabStripStateString(tabstrip())); |
| |
| // Test MoveWebContentsAt, select_after_move == false |
| { |
| tabstrip()->MoveWebContentsAt(1, 0, false); |
| EXPECT_EQ(2, observer()->GetStateCount()); |
| State s1(raw_contents1, 0, MockTabStripModelObserver::MOVE); |
| s1.src_index = 1; |
| observer()->ExpectStateEquals(0, s1); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| |
| tabstrip()->MoveWebContentsAt(0, 1, false); |
| observer()->ClearStates(); |
| } |
| EXPECT_EQ("2 1", GetTabStripStateString(tabstrip())); |
| |
| // Test Getters |
| { |
| EXPECT_EQ(raw_contents2, tabstrip()->GetActiveWebContents()); |
| EXPECT_EQ(raw_contents2, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_contents1, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(0, tabstrip()->GetIndexOfWebContents(raw_contents2)); |
| EXPECT_EQ(1, tabstrip()->GetIndexOfWebContents(raw_contents1)); |
| } |
| |
| // Test UpdateWebContentsStateAt |
| { |
| tabstrip()->UpdateWebContentsStateAt(0, TabChangeType::kAll); |
| EXPECT_EQ(1, observer()->GetStateCount()); |
| State s1(raw_contents2, 0, MockTabStripModelObserver::CHANGE); |
| observer()->ExpectStateEquals(0, s1); |
| observer()->ClearStates(); |
| } |
| |
| // Test SelectNextTab, SelectPreviousTab, SelectLastTab |
| { |
| // Make sure the second of the two tabs is selected first... |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->SelectPreviousTab(); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| tabstrip()->SelectLastTab(); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| tabstrip()->SelectNextTab(); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| } |
| |
| // Test CloseSelectedTabs |
| { |
| tabstrip()->CloseSelectedTabs(); |
| // |CloseSelectedTabs| calls CloseWebContentsAt, we already tested that, now |
| // just verify that the count and selected index have changed |
| // appropriately... |
| EXPECT_EQ(1, tabstrip()->count()); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| } |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| |
| int close_all_count = 0, close_all_canceled_count = 0, |
| close_all_completed_count = 0; |
| observer()->GetCloseCounts(&close_all_count, &close_all_canceled_count, |
| &close_all_completed_count); |
| EXPECT_EQ(1, close_all_count); |
| EXPECT_EQ(0, close_all_canceled_count); |
| EXPECT_EQ(1, close_all_completed_count); |
| |
| // TabStripModel should now be empty. |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Opener methods are tested below... |
| |
| tabstrip()->RemoveObserver(observer()); |
| } |
| |
| TEST_F(TabStripModelTest, TestTabHandlesStaticTabstrip) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), true); |
| const tabs::TabHandle handle1 = tabstrip()->GetTabAtIndex(0)->GetHandle(); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), true); |
| const tabs::TabHandle handle2 = tabstrip()->GetTabAtIndex(1)->GetHandle(); |
| |
| EXPECT_EQ(0, tabstrip()->GetIndexOfTab(handle1.Get())); |
| EXPECT_EQ(handle1, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| EXPECT_EQ(1, tabstrip()->GetIndexOfTab(handle2.Get())); |
| EXPECT_EQ(handle2, tabstrip()->GetTabAtIndex(1)->GetHandle()); |
| } |
| |
| TEST_F(TabStripModelTest, TestTabHandlesMovingTabInSameTabstrip) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), true); |
| const tabs::TabHandle handle1 = tabstrip()->GetTabAtIndex(0)->GetHandle(); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), true); |
| const tabs::TabHandle handle2 = tabstrip()->GetTabAtIndex(1)->GetHandle(); |
| |
| tabstrip()->MoveWebContentsAt(0, 1, false); |
| |
| EXPECT_EQ(0, tabstrip()->GetIndexOfTab(handle2.Get())); |
| EXPECT_EQ(handle2, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| EXPECT_EQ(1, tabstrip()->GetIndexOfTab(handle1.Get())); |
| EXPECT_EQ(handle1, tabstrip()->GetTabAtIndex(1)->GetHandle()); |
| } |
| |
| TEST_F(TabStripModelTest, TestTabHandlesTabClosed) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), true); |
| const tabs::TabHandle handle = tabstrip()->GetTabAtIndex(0)->GetHandle(); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), true); |
| |
| tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_NONE); |
| |
| EXPECT_EQ(TabStripModel::kNoTab, tabstrip()->GetIndexOfTab(handle.Get())); |
| EXPECT_EQ(nullptr, handle.Get()); |
| } |
| |
| TEST_F(TabStripModelTest, TestTabHandlesOutOfBounds) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), true); |
| |
| EXPECT_EQ(TabStripModel::kNoTab, tabstrip()->GetIndexOfTab(nullptr)); |
| EXPECT_DEATH_IF_SUPPORTED(tabstrip()->GetTabAtIndex(2)->GetHandle(), ""); |
| EXPECT_DEATH_IF_SUPPORTED(tabstrip()->GetTabAtIndex(-1)->GetHandle(), ""); |
| } |
| |
| TEST_F(TabStripModelTest, TestTabHandlesAcrossModels) { |
| MockBrowserWindowInterface bwi; |
| delegate()->SetBrowserWindowInterface(&bwi); |
| ON_CALL(bwi, GetTabStripModel()).WillByDefault(::testing::Return(tabstrip())); |
| |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), true); |
| const tabs::TabHandle handle = tabstrip()->GetTabAtIndex(0)->GetHandle(); |
| content::WebContents* raw_contents = handle.Get()->GetContents(); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), true); |
| content::WebContents* const opener = tabstrip()->GetWebContentsAt(1); |
| |
| ASSERT_EQ(0, tabstrip()->GetIndexOfTab(handle.Get())); |
| ASSERT_EQ(handle, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| ASSERT_EQ(tabstrip(), |
| handle.Get()->GetBrowserWindowInterface()->GetTabStripModel()); |
| |
| tabstrip()->SetOpenerOfWebContentsAt(0, opener); |
| ASSERT_NE(nullptr, tabstrip()->GetOpenerOfTabAt(0)); |
| ASSERT_EQ(opener, tabstrip()->GetOpenerOfTabAt(0)->GetContents()); |
| tabstrip()->SetTabPinned(0, true); |
| ASSERT_EQ(true, handle.Get()->IsPinned()); |
| tabstrip()->SetTabBlocked(0, true); |
| ASSERT_EQ(true, tabstrip()->IsTabBlocked(0)); |
| |
| // Detach the tab, and the TabModel should continue to exist, but its state |
| // should get mostly reset. |
| |
| std::unique_ptr<tabs::TabModel> owned_tab = |
| tabstrip()->DetachTabAtForInsertion(0); |
| EXPECT_EQ(owned_tab.get(), handle.Get()); |
| // Tabs not in a tabstrip are not supported - this should CHECK. |
| EXPECT_DEATH_IF_SUPPORTED(handle.Get()->GetBrowserWindowInterface(), ""); |
| |
| EXPECT_EQ(raw_contents, handle.Get()->GetContents()); |
| EXPECT_EQ(nullptr, owned_tab.get()->opener()); |
| EXPECT_EQ(false, owned_tab.get()->reset_opener_on_active_tab_change()); |
| EXPECT_EQ(false, handle.Get()->IsPinned()); |
| EXPECT_EQ(false, owned_tab.get()->blocked()); |
| |
| // Add it back into the tabstrip()-> |
| |
| tabstrip()->InsertDetachedTabAt(0, std::move(owned_tab), |
| AddTabTypes::ADD_NONE); |
| EXPECT_EQ(tabstrip(), |
| handle.Get()->GetBrowserWindowInterface()->GetTabStripModel()); |
| |
| EXPECT_EQ(raw_contents, handle.Get()->GetContents()); |
| EXPECT_EQ(nullptr, tabstrip()->GetOpenerOfTabAt(0)); |
| EXPECT_EQ(false, handle.Get()->IsPinned()); |
| EXPECT_EQ(false, tabstrip()->IsTabBlocked(0)); |
| |
| delegate()->SetBrowserWindowInterface(nullptr); |
| } |
| |
| TEST_F(TabStripModelTest, TestDetachSplitInGroupNewSelection) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(4), true); |
| |
| EXPECT_EQ(tabstrip()->count(), 4); |
| |
| tabstrip()->AddToNewGroup(std::vector<int>{1, 2, 3}); |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| split_tabs::SplitTabId split_id = tabstrip()->AddToNewSplit( |
| std::vector<int>{3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ForgetAllOpeners(); |
| |
| tabstrip()->DetachSplitTabForInsertion(split_id); |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| } |
| |
| TEST_F(TabStripModelTest, TestDetachTabInGroupNewSelection) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(4), true); |
| |
| EXPECT_EQ(tabstrip()->count(), 4); |
| |
| tabstrip()->AddToNewGroup(std::vector<int>{1, 2}); |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->ForgetAllOpeners(); |
| |
| tabstrip()->DetachTabAtForInsertion(2); |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| } |
| |
| TEST_F(TabStripModelTest, TestDetachTabInSplitNewSelection) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(4), true); |
| EXPECT_EQ(tabstrip()->count(), 4); |
| tabstrip()->AddToNewGroup(std::vector<int>{1, 2, 3}); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ForgetAllOpeners(); |
| |
| tabstrip()->DetachTabAtForInsertion(1); |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| } |
| |
| TEST_F(TabStripModelTest, TestDetachSplitForInsertion) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(4), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(5), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(6), true); |
| |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_id = tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| tabstrip()->ForgetAllOpeners(); |
| |
| std::unique_ptr<DetachedTabCollection> detached_split = |
| tabstrip()->DetachSplitTabForInsertion(split_id); |
| |
| tabs::SplitTabCollection* split_collection = |
| std::get<std::unique_ptr<tabs::SplitTabCollection>>( |
| detached_split->collection_) |
| .get(); |
| |
| EXPECT_EQ(split_collection->TabCountRecursive(), 2u); |
| EXPECT_FALSE(tabstrip()->ContainsSplit(split_id)); |
| EXPECT_EQ(tabstrip()->count(), 4); |
| EXPECT_EQ(tabstrip()->active_index(), 2); |
| // Reinsert the detached group. |
| tabstrip()->InsertDetachedSplitTabAt(std::move(detached_split), 0, false); |
| |
| EXPECT_TRUE(tabstrip()->ContainsSplit(split_id)); |
| EXPECT_EQ(tabstrip()->GetSplitData(split_id)->ListTabs().size(), 2u); |
| EXPECT_EQ(tabstrip()->GetTabAtIndex(0)->GetSplit().value(), split_id); |
| EXPECT_EQ(tabstrip()->GetTabAtIndex(1)->GetSplit().value(), split_id); |
| EXPECT_EQ(tabstrip()->count(), 6); |
| } |
| |
| TEST_F(TabStripModelTest, TestDetachGroupForInsertion) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(4), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(5), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(6), true); |
| |
| tab_groups::TabGroupId group_id = |
| tabstrip()->AddToNewGroup(std::vector<int>{1, 2}); |
| std::unique_ptr<DetachedTabCollection> detached_group = |
| tabstrip()->DetachTabGroupForInsertion(group_id); |
| tabs::TabGroupTabCollection* group_collection = |
| std::get<std::unique_ptr<tabs::TabGroupTabCollection>>( |
| detached_group->collection_) |
| .get(); |
| |
| EXPECT_EQ(group_collection->TabCountRecursive(), 2u); |
| EXPECT_FALSE(tabstrip()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ(tabstrip()->count(), 4); |
| |
| // Reinsert the detached group. |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), 0); |
| |
| EXPECT_TRUE(tabstrip()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group_id)->ListTabs().length(), |
| 2u); |
| EXPECT_EQ(tabstrip()->GetTabAtIndex(0)->GetGroup().value(), group_id); |
| EXPECT_EQ(tabstrip()->GetTabAtIndex(1)->GetGroup().value(), group_id); |
| EXPECT_EQ(tabstrip()->count(), 6); |
| } |
| |
| TEST_F(TabStripModelTest, TestInsertDetachGroupStartOfTabstrip) { |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2}); |
| |
| tab_groups::TabGroupId group_id = |
| tabstrip()->AddToNewGroup(std::vector<int>{3, 4}); |
| std::unique_ptr<DetachedTabCollection> detached_group = |
| tabstrip()->DetachTabGroupForInsertion(group_id); |
| tabs::TabGroupTabCollection* group_collection = |
| std::get<std::unique_ptr<tabs::TabGroupTabCollection>>( |
| detached_group->collection_) |
| .get(); |
| |
| EXPECT_EQ(group_collection->TabCountRecursive(), 2u); |
| EXPECT_FALSE(tabstrip()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ(tabstrip()->count(), 3); |
| |
| // Reinsert the detached group. |
| gfx::Range insert_indices = |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), 0); |
| |
| EXPECT_TRUE(tabstrip()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group_id)->ListTabs().length(), |
| 2u); |
| EXPECT_EQ(tabstrip()->count(), 5); |
| |
| // group is inserted after the pinned container. |
| EXPECT_EQ(insert_indices, gfx::Range(2, 4)); |
| } |
| |
| TEST_F(TabStripModelTest, TestDetachGroupNewSelection) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(4), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(5), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(6), true); |
| |
| tab_groups::TabGroupId group_id = |
| tabstrip()->AddToNewGroup(std::vector<int>{1, 2}); |
| |
| // Test the first preference is a tab the group opened. |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->ForgetAllOpeners(); |
| tabstrip()->SetOpenerOfWebContentsAt(0, tabstrip()->GetWebContentsAt(1)); |
| std::unique_ptr<DetachedTabCollection> detached_group = |
| tabstrip()->DetachTabGroupForInsertion(group_id); |
| |
| EXPECT_EQ(tabstrip()->active_index(), 0); |
| |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), 1); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->ForgetAllOpeners(); |
| tabstrip()->SetOpenerOfWebContentsAt(4, tabstrip()->GetWebContentsAt(1)); |
| detached_group = tabstrip()->DetachTabGroupForInsertion(group_id); |
| |
| EXPECT_EQ(tabstrip()->active_index(), 2); |
| |
| // Test the second preference is a tab the group's opener opened. |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), 1); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->ForgetAllOpeners(); |
| tabstrip()->SetOpenerOfWebContentsAt(2, tabstrip()->GetWebContentsAt(3)); |
| tabstrip()->SetOpenerOfWebContentsAt(1, tabstrip()->GetWebContentsAt(4)); |
| tabstrip()->SetOpenerOfWebContentsAt(0, tabstrip()->GetWebContentsAt(4)); |
| |
| detached_group = tabstrip()->DetachTabGroupForInsertion(group_id); |
| EXPECT_EQ(tabstrip()->active_index(), 0); |
| |
| // Test the third preference is the group's opener. |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), 1); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->ForgetAllOpeners(); |
| tabstrip()->SetOpenerOfWebContentsAt(2, tabstrip()->GetWebContentsAt(3)); |
| |
| detached_group = tabstrip()->DetachTabGroupForInsertion(group_id); |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| |
| // Next preference is the tab after the last tab of the group. |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), 2); |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->ForgetAllOpeners(); |
| |
| detached_group = tabstrip()->DetachTabGroupForInsertion(group_id); |
| EXPECT_EQ(tabstrip()->active_index(), 2); |
| |
| // Last preference is the tab before the first tab of the group. |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), |
| tabstrip()->count()); |
| tabstrip()->ActivateTabAt(5); |
| tabstrip()->ForgetAllOpeners(); |
| |
| detached_group = tabstrip()->DetachTabGroupForInsertion(group_id); |
| EXPECT_EQ(tabstrip()->active_index(), 3); |
| } |
| |
| TEST_F(TabStripModelTest, TestDetachAndInsertGroupWithSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| |
| tab_groups::TabGroupId group_id = |
| tabstrip()->AddToNewGroup(std::vector<int>{1, 2, 3}); |
| |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| split_tabs::SplitTabId split_id = tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| std::unique_ptr<DetachedTabCollection> detached_group = |
| tabstrip()->DetachTabGroupForInsertion(group_id); |
| tabs::TabGroupTabCollection* group_collection = |
| std::get<std::unique_ptr<tabs::TabGroupTabCollection>>( |
| detached_group->collection_) |
| .get(); |
| |
| EXPECT_EQ(group_collection->TabCountRecursive(), 3u); |
| EXPECT_FALSE(tabstrip()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ(tabstrip()->count(), 2); |
| |
| // Reinsert the detached group. |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), 0); |
| |
| EXPECT_TRUE(tabstrip()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group_id)->ListTabs().length(), |
| 3u); |
| EXPECT_EQ(tabstrip()->group_model()->GetTabGroup(group_id)->ListTabs(), |
| gfx::Range(0, 3)); |
| std::vector<tabs::TabInterface*> expected_split_tabs = { |
| tabstrip()->GetTabAtIndex(1), tabstrip()->GetTabAtIndex(2)}; |
| EXPECT_EQ(tabstrip()->GetSplitData(split_id)->ListTabs(), |
| expected_split_tabs); |
| EXPECT_EQ(tabstrip()->count(), 5); |
| EXPECT_EQ("1 2s 3s 0 4", GetTabStripStateString(tabstrip())); |
| } |
| |
| TEST_F(TabStripModelTest, InsertDetachedGroupSelectionObserver) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(4), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(5), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(6), false); |
| |
| tab_groups::TabGroupId group_id = |
| tabstrip()->AddToNewGroup(std::vector<int>{1, 2}); |
| tabstrip()->ActivateTabAt(1); |
| std::unique_ptr<DetachedTabCollection> detached_group = |
| tabstrip()->DetachTabGroupForInsertion(group_id); |
| |
| tabs::TabInterface* active_tab = tabstrip()->GetActiveTab(); |
| observer()->ClearStates(); |
| |
| tabstrip()->InsertDetachedTabGroupAt(std::move(detached_group), |
| tabstrip()->count()); |
| ObservedSelectionChange change = observer()->GetLatestSelectionChange(); |
| |
| EXPECT_EQ(active_tab->GetHandle(), change.old_tab); |
| EXPECT_EQ(tabstrip()->GetActiveTab()->GetHandle(), change.new_tab); |
| EXPECT_EQ(tabstrip()->active_index(), 4); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, TestBasicOpenerAPI) { |
| // This is a basic test of opener functionality. opener is created |
| // as the first tab in the strip and then we create 5 other tabs in the |
| // background with opener set as their opener. |
| |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| WebContents* raw_opener = opener.get(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| WebContents* raw_contents1 = contents1.get(); |
| std::unique_ptr<WebContents> contents2 = CreateWebContents(); |
| std::unique_ptr<WebContents> contents3 = CreateWebContents(); |
| std::unique_ptr<WebContents> contents4 = CreateWebContents(); |
| std::unique_ptr<WebContents> contents5 = CreateWebContents(); |
| WebContents* raw_contents5 = contents5.get(); |
| |
| // We use |InsertWebContentsAt| here instead of |AppendWebContents| so that |
| // openership relationships are preserved. |
| tabstrip()->InsertWebContentsAt(tabstrip()->count(), std::move(contents1), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| tabstrip()->InsertWebContentsAt(tabstrip()->count(), std::move(contents2), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| tabstrip()->InsertWebContentsAt(tabstrip()->count(), std::move(contents3), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| tabstrip()->InsertWebContentsAt(tabstrip()->count(), std::move(contents4), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| tabstrip()->InsertWebContentsAt(tabstrip()->count(), std::move(contents5), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| |
| // All the tabs should have the same opener. |
| for (int i = 1; i < tabstrip()->count(); ++i) { |
| const tabs::TabInterface* tab_opener = tabstrip()->GetOpenerOfTabAt(i); |
| EXPECT_EQ(raw_opener, tab_opener ? tab_opener->GetContents() : nullptr); |
| } |
| |
| // If there is a next adjacent item, then the index should be of that item. |
| EXPECT_EQ(2, tabstrip()->GetIndexOfNextWebContentsOpenedByOpenerOf( |
| gfx::Range(1, 2))); |
| // If the last tab in the opener tree is closed, the preceding tab in the same |
| // tree should be selected. |
| EXPECT_EQ(4, tabstrip()->GetIndexOfNextWebContentsOpenedByOpenerOf( |
| gfx::Range(5, 6))); |
| |
| // Tests the method that finds the last tab opened by the same opener in the |
| // strip (this is the insertion index for the next background tab for the |
| // specified opener). |
| EXPECT_EQ(5, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener, 1)); |
| |
| // For a tab that has opened no other tabs, the return value should always be |
| // -1... |
| EXPECT_EQ(-1, |
| tabstrip()->GetIndexOfNextWebContentsOpenedBy(gfx::Range(1, 2))); |
| EXPECT_EQ(-1, |
| tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_contents1, 3)); |
| |
| // ForgetAllOpeners should destroy all opener relationships. |
| tabstrip()->ForgetAllOpeners(); |
| EXPECT_EQ(-1, |
| tabstrip()->GetIndexOfNextWebContentsOpenedBy(gfx::Range(1, 2))); |
| EXPECT_EQ(-1, |
| tabstrip()->GetIndexOfNextWebContentsOpenedBy(gfx::Range(5, 6))); |
| EXPECT_EQ(-1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener, 1)); |
| |
| // Specify the last tab as the opener of the others. |
| for (int i = 0; i < tabstrip()->count() - 1; ++i) { |
| tabstrip()->SetOpenerOfWebContentsAt(i, raw_contents5); |
| } |
| |
| for (int i = 0; i < tabstrip()->count() - 1; ++i) { |
| const tabs::TabInterface* tab_opener = tabstrip()->GetOpenerOfTabAt(i); |
| EXPECT_EQ(raw_contents5, tab_opener ? tab_opener->GetContents() : nullptr); |
| } |
| |
| // If there is a next adjacent item, then the index should be of that item. |
| EXPECT_EQ(2, tabstrip()->GetIndexOfNextWebContentsOpenedByOpenerOf( |
| gfx::Range(1, 2))); |
| |
| // If the last tab in the opener tree is closed, the preceding tab in the same |
| // opener tree should be selected. |
| EXPECT_EQ(3, tabstrip()->GetIndexOfNextWebContentsOpenedByOpenerOf( |
| gfx::Range(4, 5))); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| static int GetInsertionIndex(TabStripModel* tabstrip) { |
| return tabstrip->DetermineInsertionIndex(ui::PAGE_TRANSITION_LINK, false); |
| } |
| |
| static void InsertWebContentses(TabStripModel* tabstrip, |
| std::unique_ptr<WebContents> contents1, |
| std::unique_ptr<WebContents> contents2, |
| std::unique_ptr<WebContents> contents3) { |
| tabstrip->InsertWebContentsAt(GetInsertionIndex(tabstrip), |
| std::move(contents1), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| tabstrip->InsertWebContentsAt(GetInsertionIndex(tabstrip), |
| std::move(contents2), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| tabstrip->InsertWebContentsAt(GetInsertionIndex(tabstrip), |
| std::move(contents3), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| } |
| |
| static bool IsSiteInContentSettingExceptionList( |
| HostContentSettingsMap* settings, |
| GURL& url, |
| ContentSettingsType type) { |
| content_settings::SettingInfo info; |
| settings->GetWebsiteSetting(url, url, type, &info); |
| auto pattern = ContentSettingsPattern::FromURLNoWildcard(url); |
| return info.primary_pattern.Compare(pattern) == |
| ContentSettingsPattern::IDENTITY; |
| } |
| |
| // Tests opening background tabs. |
| TEST_F(TabStripModelTest, TestLTRInsertionOptions) { |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| WebContents* raw_contents1 = contents1.get(); |
| std::unique_ptr<WebContents> contents2 = CreateWebContents(); |
| WebContents* raw_contents2 = contents2.get(); |
| std::unique_ptr<WebContents> contents3 = CreateWebContents(); |
| WebContents* raw_contents3 = contents3.get(); |
| |
| // Test LTR |
| InsertWebContentses(tabstrip(), std::move(contents1), std::move(contents2), |
| std::move(contents3)); |
| EXPECT_EQ(raw_contents1, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_contents2, tabstrip()->GetWebContentsAt(2)); |
| EXPECT_EQ(raw_contents3, tabstrip()->GetWebContentsAt(3)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // This test constructs a tabstrip, and then simulates loading several tabs in |
| // the background from link clicks on the first tab. Then it simulates opening |
| // a new tab from the first tab in the foreground via a link click, verifies |
| // that this tab is opened adjacent to the opener, then closes it. |
| // Finally it tests that a tab opened for some non-link purpose opens at the |
| // end of the strip, not bundled to any existing context. |
| TEST_F(TabStripModelTest, TestInsertionIndexDetermination) { |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| WebContents* raw_opener = opener.get(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| |
| // Open some other random unrelated tab in the background to monkey with our |
| // insertion index. |
| std::unique_ptr<WebContents> other = CreateWebContents(); |
| WebContents* raw_other = other.get(); |
| tabstrip()->AppendWebContents(std::move(other), false); |
| |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| WebContents* raw_contents1 = contents1.get(); |
| std::unique_ptr<WebContents> contents2 = CreateWebContents(); |
| WebContents* raw_contents2 = contents2.get(); |
| std::unique_ptr<WebContents> contents3 = CreateWebContents(); |
| WebContents* raw_contents3 = contents3.get(); |
| |
| // Start by testing LTR. |
| InsertWebContentses(tabstrip(), std::move(contents1), std::move(contents2), |
| std::move(contents3)); |
| EXPECT_EQ(raw_opener, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_contents1, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_contents2, tabstrip()->GetWebContentsAt(2)); |
| EXPECT_EQ(raw_contents3, tabstrip()->GetWebContentsAt(3)); |
| EXPECT_EQ(raw_other, tabstrip()->GetWebContentsAt(4)); |
| |
| // The opener API should work... |
| EXPECT_EQ(3, tabstrip()->GetIndexOfNextWebContentsOpenedByOpenerOf( |
| gfx::Range(2, 3))); |
| EXPECT_EQ(2, tabstrip()->GetIndexOfNextWebContentsOpenedByOpenerOf( |
| gfx::Range(3, 4))); |
| EXPECT_EQ(3, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener, 1)); |
| |
| // Now open a foreground tab from a link. It should be opened adjacent to the |
| // opener tab. |
| std::unique_ptr<WebContents> fg_link_contents = CreateWebContents(); |
| WebContents* raw_fg_link_contents = fg_link_contents.get(); |
| int insert_index = |
| tabstrip()->DetermineInsertionIndex(ui::PAGE_TRANSITION_LINK, true); |
| EXPECT_EQ(1, insert_index); |
| tabstrip()->InsertWebContentsAt( |
| insert_index, std::move(fg_link_contents), |
| AddTabTypes::ADD_ACTIVE | AddTabTypes::ADD_INHERIT_OPENER); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| EXPECT_EQ(raw_fg_link_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Now close this contents. The selection should move to the opener contents. |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| // Now open a new empty tab. It should open at the end of the tabstrip()-> |
| std::unique_ptr<WebContents> fg_nonlink_contents = CreateWebContents(); |
| WebContents* raw_fg_nonlink_contents = fg_nonlink_contents.get(); |
| insert_index = tabstrip()->DetermineInsertionIndex( |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, true); |
| EXPECT_EQ(tabstrip()->count(), insert_index); |
| // We break the opener relationship... |
| tabstrip()->InsertWebContentsAt(insert_index, std::move(fg_nonlink_contents), |
| AddTabTypes::ADD_NONE); |
| // Now select it, so that GestureType != kNone causes the opener |
| // relationship to be forgotten... |
| tabstrip()->ActivateTabAt( |
| tabstrip()->count() - 1, |
| TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(tabstrip()->count() - 1, tabstrip()->active_index()); |
| EXPECT_EQ(raw_fg_nonlink_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Verify that all opener relationships are forgotten. |
| EXPECT_EQ(-1, |
| tabstrip()->GetIndexOfNextWebContentsOpenedBy(gfx::Range(2, 3))); |
| EXPECT_EQ(-1, |
| tabstrip()->GetIndexOfNextWebContentsOpenedBy(gfx::Range(3, 4))); |
| EXPECT_EQ(-1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener, 1)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that non-adjacent tabs with an opener are ignored when deciding where |
| // to position tabs. |
| TEST_F(TabStripModelTest, TestInsertionIndexDeterminationAfterDragged) { |
| // Start with three tabs, of which the first is active. |
| std::unique_ptr<WebContents> opener1 = CreateWebContentsWithID(1); |
| WebContents* raw_opener1 = opener1.get(); |
| tabstrip()->AppendWebContents(std::move(opener1), true /* foreground */); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(3), false); |
| EXPECT_EQ("1 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| EXPECT_EQ(-1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| |
| // Open a link in a new background tab. |
| tabstrip()->InsertWebContentsAt(GetInsertionIndex(tabstrip()), |
| CreateWebContentsWithID(11), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| EXPECT_EQ("1 11 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| EXPECT_EQ(1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| |
| // Drag that tab (which activates it) one to the right. |
| tabstrip()->MoveWebContentsAt(1, 2, true /* select_after_move */); |
| EXPECT_EQ("1 2 11 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(11, GetID(tabstrip()->GetActiveWebContents())); |
| // It should no longer be counted by GetIndexOfLastWebContentsOpenedBy, |
| // since there is a tab in between, even though its opener is unchanged. |
| // TODO(johnme): Maybe its opener should be reset when it's dragged away. |
| EXPECT_EQ(-1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| |
| tabs::TabInterface* tab_opener = tabstrip()->GetOpenerOfTabAt(2); |
| EXPECT_EQ(raw_opener1, tab_opener ? tab_opener->GetContents() : nullptr); |
| |
| // Activate the parent tab again. |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| |
| // Open another link in a new background tab. |
| tabstrip()->InsertWebContentsAt(GetInsertionIndex(tabstrip()), |
| CreateWebContentsWithID(12), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| // Tab 12 should be next to 1, and considered opened by it. |
| EXPECT_EQ("1 12 2 11 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| EXPECT_EQ(1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that grandchild tabs are considered to be opened by their grandparent |
| // tab when deciding where to position tabs. |
| TEST_F(TabStripModelTest, TestInsertionIndexDeterminationNestedOpener) { |
| // Start with two tabs, of which the first is active: |
| std::unique_ptr<WebContents> opener1 = CreateWebContentsWithID(1); |
| WebContents* raw_opener1 = opener1.get(); |
| tabstrip()->AppendWebContents(std::move(opener1), true /* foreground */); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| EXPECT_EQ("1 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| EXPECT_EQ(-1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| |
| // Open a link in a new background child tab. |
| std::unique_ptr<WebContents> child11 = CreateWebContentsWithID(11); |
| WebContents* raw_child11 = child11.get(); |
| tabstrip()->InsertWebContentsAt(GetInsertionIndex(tabstrip()), |
| std::move(child11), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| EXPECT_EQ("1 11 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| EXPECT_EQ(1, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| |
| // Activate the child tab: |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(11, GetID(tabstrip()->GetActiveWebContents())); |
| |
| // Open a link in a new background grandchild tab. |
| tabstrip()->InsertWebContentsAt(GetInsertionIndex(tabstrip()), |
| CreateWebContentsWithID(111), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| EXPECT_EQ("1 11 111 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(11, GetID(tabstrip()->GetActiveWebContents())); |
| // The grandchild tab should be counted by GetIndexOfLastWebContentsOpenedBy |
| // as opened by both its parent (child11) and grandparent (opener1). |
| EXPECT_EQ(2, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| EXPECT_EQ(2, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_child11, 1)); |
| |
| // Activate the parent tab again: |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| |
| // Open another link in a new background child tab (a sibling of child11). |
| tabstrip()->InsertWebContentsAt(GetInsertionIndex(tabstrip()), |
| CreateWebContentsWithID(12), |
| AddTabTypes::ADD_INHERIT_OPENER); |
| EXPECT_EQ("1 11 111 12 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| // opener1 has three adjacent descendants (11, 111, 12) |
| EXPECT_EQ(3, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| // child11 has only one adjacent descendant (111) |
| EXPECT_EQ(2, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_child11, 1)); |
| |
| // Closing a tab should cause its children to inherit the tab's opener. |
| int previous_tab_count = tabstrip()->count(); |
| tabstrip()->CloseWebContentsAt( |
| 1, TabCloseTypes::CLOSE_USER_GESTURE | |
| TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB); |
| EXPECT_EQ(previous_tab_count - 1, tabstrip()->count()); |
| EXPECT_EQ("1 111 12 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(1, GetID(tabstrip()->GetActiveWebContents())); |
| // opener1 is now the opener of 111, so has two adjacent descendants (111, 12) |
| tabs::TabInterface* tab_opener = tabstrip()->GetOpenerOfTabAt(1); |
| EXPECT_EQ(raw_opener1, tab_opener ? tab_opener->GetContents() : nullptr); |
| EXPECT_EQ(2, tabstrip()->GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that selection is shifted to the correct tab when a tab is closed. |
| // If the tab is in the background when it is closed, the selection does not |
| // change. |
| TEST_F(TabStripModelTest, CloseInactiveTabKeepsSelection) { |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| InsertWebContentses(tabstrip(), CreateWebContents(), CreateWebContents(), |
| CreateWebContents()); |
| ASSERT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that selection is shifted to the correct tab when a tab is closed. |
| // If the tab doesn't have an opener or a group, selection shifts to the right. |
| TEST_F(TabStripModelTest, CloseActiveTabShiftsSelectionRight) { |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| InsertWebContentses(tabstrip(), CreateWebContents(), CreateWebContents(), |
| CreateWebContents()); |
| ASSERT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->ForgetAllOpeners(); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(1, tabstrip()->active_index()); |
| |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that selection is shifted to the correct tab when a tab is closed. |
| // If the tab doesn't have an opener but is in a group, selection shifts to |
| // another tab in the same group. |
| TEST_F(TabStripModelTest, CloseGroupedTabShiftsSelectionWithinGroup) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| InsertWebContentses(tabstrip(), CreateWebContents(), CreateWebContents(), |
| CreateWebContents()); |
| ASSERT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->ForgetAllOpeners(); |
| tabstrip()->AddToNewGroup({0, 1, 2}); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(1, tabstrip()->active_index()); |
| |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that the active selection will change to another non collapsed tab when |
| // the active index is in the collapsing group. |
| TEST_F(TabStripModelTest, CollapseGroupShiftsSelection_SuccessNextTab) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| InsertWebContentses(tabstrip(), CreateWebContents(), CreateWebContents(), |
| CreateWebContents()); |
| ASSERT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->ForgetAllOpeners(); |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0, 1}); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(1, tabstrip()->active_index()); |
| |
| std::optional<int> next_active = tabstrip()->GetNextExpandedActiveTab(group); |
| |
| EXPECT_EQ(2, next_active); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that the active selection will change to another non collapsed tab when |
| // the active index is in the collapsing group. |
| TEST_F(TabStripModelTest, CollapseGroupShiftsSelection_SuccessPreviousTab) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| InsertWebContentses(tabstrip(), CreateWebContents(), CreateWebContents(), |
| CreateWebContents()); |
| ASSERT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->ForgetAllOpeners(); |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({2, 3}); |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(2, tabstrip()->active_index()); |
| |
| std::optional<int> next_active = tabstrip()->GetNextExpandedActiveTab(group); |
| |
| EXPECT_EQ(1, next_active); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that there is no valid selection to shift to when the active tab is in |
| // the group that will be collapsed. |
| TEST_F(TabStripModelTest, CollapseGroupShiftsSelection_NoAvailableTabs) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| InsertWebContentses(tabstrip(), CreateWebContents(), CreateWebContents(), |
| CreateWebContents()); |
| ASSERT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->ForgetAllOpeners(); |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0, 1, 2, 3}); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(1, tabstrip()->active_index()); |
| |
| std::optional<int> next_active = tabstrip()->GetNextExpandedActiveTab(group); |
| |
| EXPECT_EQ(std::nullopt, next_active); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that selection is shifted to the correct tab when a tab is closed. |
| // If the tab does have an opener, selection shifts to the next tab opened by |
| // the same opener scanning LTR. |
| TEST_F(TabStripModelTest, CloseTabWithOpenerShiftsSelectionWithinOpened) { |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| InsertWebContentses(tabstrip(), CreateWebContents(), CreateWebContents(), |
| CreateWebContents()); |
| ASSERT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(2, tabstrip()->active_index()); |
| |
| tabstrip()->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(2, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that selection is shifted to the correct tab when a tab is closed. |
| // If the tab has an opener but no "siblings" opened by the same opener, |
| // selection shifts to the opener itself. |
| TEST_F(TabStripModelTest, CloseTabWithOpenerShiftsSelectionToOpener) { |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| std::unique_ptr<WebContents> other_contents = CreateWebContents(); |
| tabstrip()->InsertWebContentsAt(1, std::move(other_contents), |
| AddTabTypes::ADD_NONE); |
| ASSERT_EQ(2, tabstrip()->count()); |
| std::unique_ptr<WebContents> opened_contents = CreateWebContents(); |
| tabstrip()->InsertWebContentsAt( |
| 2, std::move(opened_contents), |
| AddTabTypes::ADD_ACTIVE | AddTabTypes::ADD_INHERIT_OPENER); |
| ASSERT_EQ(2, tabstrip()->active_index()); |
| |
| tabstrip()->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, IsContextMenuCommandEnabledBadIndex) { |
| constexpr int kTestTabCount = 1; |
| |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), kTestTabCount, 0, {0})); |
| ASSERT_EQ(kTestTabCount, tabstrip()->count()); |
| ASSERT_FALSE(tabstrip()->ContainsIndex(kTestTabCount)); |
| |
| // kNoTabs should return false for context menu commands being enabled. |
| EXPECT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| TabStripModel::kNoTab, TabStripModel::CommandCloseTab)); |
| |
| // Indexes out of bounds should return false for context menu |
| // commands being enabled. |
| EXPECT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| kTestTabCount, TabStripModel::CommandCloseTab)); |
| } |
| |
| // Tests IsContextMenuCommandEnabled and ExecuteContextMenuCommand with |
| // CommandCloseTab. |
| TEST_F(TabStripModelTest, CommandCloseTab) { |
| // Make sure can_close is honored. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 1, 0, {0})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandCloseTab)); |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandCloseTab); |
| ASSERT_TRUE(tabstrip()->empty()); |
| |
| // Make sure close on a tab that is selected affects all the selected tabs. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {0, 1})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandCloseTab)); |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandCloseTab); |
| // Should have closed tabs 0 and 1. |
| EXPECT_EQ("2", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Select two tabs and make close on a tab that isn't selected doesn't affect |
| // selected tabs. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {0, 1})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandCloseTab)); |
| tabstrip()->ExecuteContextMenuCommand(2, TabStripModel::CommandCloseTab); |
| // Should have closed tab 2. |
| EXPECT_EQ("0 1", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Tests with 3 tabs, one pinned, two tab selected, one of which is pinned. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 1, {0, 1})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandCloseTab)); |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandCloseTab); |
| // Should have closed tab 2. |
| EXPECT_EQ("2", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests IsContextMenuCommandEnabled and ExecuteContextMenuCommand with |
| // CommandCloseOtherTabs. |
| TEST_F(TabStripModelTest, CommandCloseOtherTabs) { |
| // Create three tabs, select two tabs, CommandCloseOtherTabs should be enabled |
| // and close two tabs. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {0, 1})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandCloseOtherTabs)); |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandCloseOtherTabs); |
| EXPECT_EQ("0 1", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Select two tabs, CommandCloseOtherTabs should be enabled and invoking it |
| // with a non-selected index should close the two other tabs. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {0, 1})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandCloseOtherTabs)); |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandCloseOtherTabs); |
| EXPECT_EQ("0 1", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Select all, CommandCloseOtherTabs should not be enabled. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {0, 1, 2})); |
| EXPECT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandCloseOtherTabs)); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Three tabs, pin one, select the two non-pinned. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 1, {1, 2})); |
| EXPECT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandCloseOtherTabs)); |
| // If we don't pass in the pinned index, the command should be enabled. |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandCloseOtherTabs)); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // 3 tabs, one pinned. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 1, {1})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandCloseOtherTabs)); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandCloseOtherTabs)); |
| tabstrip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandCloseOtherTabs); |
| // The pinned tab shouldn't be closed. |
| EXPECT_EQ("0p 1", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Unselected split tab. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 1, {1})); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(3); |
| ASSERT_EQ("0p 1s 2s 3", GetTabStripStateString(tabstrip())); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandCloseOtherTabs)); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandCloseOtherTabs)); |
| tabstrip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandCloseOtherTabs); |
| // The pinned tab and the other tab in the split shouldn't be closed. |
| EXPECT_EQ("0p 1s 2s", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests IsContextMenuCommandEnabled and ExecuteContextMenuCommand with |
| // CommandCloseTabsToRight. |
| TEST_F(TabStripModelTest, CommandCloseTabsToRight) { |
| // Create three tabs, select last two tabs, CommandCloseTabsToRight should |
| // only be enabled for the first tab. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {1, 2})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandCloseTabsToRight)); |
| EXPECT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandCloseTabsToRight)); |
| EXPECT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandCloseTabsToRight)); |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandCloseTabsToRight); |
| EXPECT_EQ("0", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| |
| // Unselected split tab. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 1, {1})); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(3); |
| ASSERT_EQ("0p 1s 2s 3", GetTabStripStateString(tabstrip())); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandCloseTabsToRight)); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandCloseTabsToRight)); |
| tabstrip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandCloseTabsToRight); |
| // The pinned tab and the other tab in the split shouldn't be closed. |
| EXPECT_EQ("0p 1s 2s", GetTabStripStateString(tabstrip())); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests IsContextMenuCommandEnabled and ExecuteContextMenuCommand with |
| // CommandTogglePinned. |
| TEST_F(TabStripModelTest, CommandTogglePinned) { |
| // Create three tabs with one pinned, pin the first two. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 1, {0, 1})); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandTogglePinned)); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandTogglePinned)); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandTogglePinned)); |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandTogglePinned); |
| EXPECT_EQ("0p 1p 2", GetTabStripStateString(tabstrip())); |
| |
| // Execute CommandTogglePinned again, this should unpin. |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandTogglePinned); |
| EXPECT_EQ("0 1 2", GetTabStripStateString(tabstrip())); |
| |
| // Pin the last. |
| tabstrip()->ExecuteContextMenuCommand(2, TabStripModel::CommandTogglePinned); |
| EXPECT_EQ("2p 0 1", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, SplitTabPinning) { |
| for (bool split_is_selected : {true, false}) { |
| for (bool use_left_tab : {true, false}) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 1, {2})); |
| tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0p 1 2s 3s 4", GetTabStripStateString(tabstrip())); |
| if (!split_is_selected) { |
| tabstrip()->ActivateTabAt(1); |
| } |
| |
| tabstrip()->ExecuteContextMenuCommand(2 + !use_left_tab, |
| TabStripModel::CommandTogglePinned); |
| EXPECT_EQ("0p 2ps 3ps 1 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->ExecuteContextMenuCommand(1 + !use_left_tab, |
| TabStripModel::CommandTogglePinned); |
| EXPECT_EQ("0p 2s 3s 1 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| } |
| } |
| |
| TEST_F(TabStripModelTest, SplitTabPinningBulk) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 12, 2, {4})); |
| tabstrip()->AddToNewSplit({5}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0p 1p 2 3 4s 5s 6 7 8 9 10 11", |
| GetTabStripStateString(tabstrip())); |
| tabstrip()->ActivateTabAt(8); |
| tabstrip()->AddToNewSplit({9}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0p 1p 2 3 4s 5s 6 7 8s 9s 10 11", |
| GetTabStripStateString(tabstrip())); |
| tabstrip()->SelectTabAt(0); |
| tabstrip()->SelectTabAt(2); |
| tabstrip()->SelectTabAt(4); |
| tabstrip()->SelectTabAt(5); |
| tabstrip()->SelectTabAt(7); |
| tabstrip()->SelectTabAt(10); |
| // tabs 0 2 4 5 7 8 9 10 should be selected |
| ASSERT_EQ(base::MakeFlatSet<size_t>(std::vector{0, 2, 4, 5, 7, 8, 9, 10}), |
| tabstrip()->selection_model().selected_indices()); |
| |
| // pin multiple selected tabs and splits |
| tabstrip()->ExecuteContextMenuCommand( |
| 5, TabStripModel::CommandTogglePinned); // tab 5 |
| EXPECT_EQ("0p 1p 2p 4ps 5ps 7p 8ps 9ps 10p 3 6 11", |
| GetTabStripStateString(tabstrip())); |
| |
| // unpin multiple selected tabs and splits |
| tabstrip()->DeselectTabAt(2); // tab 2 |
| tabstrip()->DeselectTabAt(8); // tab 10 |
| // tabs 0 4 5 7 8 9 should be selected |
| ASSERT_EQ(base::MakeFlatSet<size_t>(std::vector{0, 3, 4, 5, 6, 7}), |
| tabstrip()->selection_model().selected_indices()); |
| tabstrip()->ExecuteContextMenuCommand( |
| 0, TabStripModel::CommandTogglePinned); // tab 0 |
| EXPECT_EQ("1p 2p 10p 0 4s 5s 7 8s 9s 3 6 11", |
| GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, RestoreSplit) { |
| // Create five tabs with two pinned. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| split_tabs::SplitTabId split_id = split_tabs::SplitTabId::GenerateNew(); |
| // Add tab at index 2 and 3 to a split. |
| tabstrip()->RestoreSplit(split_id, {2, 3}, split_tabs::SplitTabVisualData()); |
| |
| EXPECT_EQ("0p 1p 2s 3s 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetSplitData(split_id)->ListTabs().size(), 2u); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, AddToSplitInGroup) { |
| // Create five tabs with two pinned. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| // Add tab at index 4 to a group. |
| tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({4}); |
| tabstrip()->ActivateTabAt( |
| 4, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0p 1p 3 2s 4s", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group_id)->ListTabs().length(), |
| 2u); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, AddToSplitInPinned) { |
| // Create five tabs with two pinned. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| // Add tab at index 4 to a group. |
| tabstrip()->AddToNewGroup({4}); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip()->AddToNewSplit({3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, AddToSplitInSelected) { |
| // Create five tabs. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0s 1s 2 3 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->active_index(), 0); |
| EXPECT_TRUE(tabstrip()->selection_model().IsSelected(0)); |
| EXPECT_TRUE(tabstrip()->selection_model().IsSelected(1)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, UnsplitOperation) { |
| // Create five tabs with two pinned. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| // Add tab at index 4 to a group. |
| tabstrip()->AddToNewGroup({4}); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->RemoveSplit(split_tab_id); |
| EXPECT_EQ("0p 3p 1p 2 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, MoveInsideSplitRemovesSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ(tabstrip()->GetSplitData(split_tab_id)->ListTabs().size(), 2u); |
| tabstrip()->MoveWebContentsAt(3, 1, false); |
| EXPECT_FALSE(tabstrip()->ContainsSplit(split_tab_id)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, MoveFromSplitRemovesSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ(tabstrip()->GetSplitData(split_tab_id)->ListTabs().size(), 2u); |
| tabstrip()->MoveWebContentsAt(1, 3, false); |
| EXPECT_FALSE(tabstrip()->ContainsSplit(split_tab_id)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabInsideSplitRemovesSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ(tabstrip()->GetSplitData(split_tab_id)->ListTabs().size(), 2u); |
| |
| std::unique_ptr<WebContents> new_blank_contents = CreateWebContents(); |
| tabstrip()->InsertWebContentsAt(1, std::move(new_blank_contents), |
| AddTabTypes::ADD_NONE); |
| EXPECT_FALSE(tabstrip()->ContainsSplit(split_tab_id)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveSplitTabRemovesEntireSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ(tabstrip()->GetSplitData(split_tab_id)->ListTabs().size(), 2u); |
| |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_FALSE(tabstrip()->ContainsSplit(split_tab_id)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, MoveGroupWithinSplitRemovesSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| |
| tabstrip()->ActivateTabAt( |
| 3, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {4}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ(tabstrip()->GetSplitData(split_tab_id)->ListTabs().size(), 2u); |
| tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({0, 1}); |
| tabstrip()->MoveGroupTo(group_id, 2); |
| EXPECT_FALSE(tabstrip()->ContainsSplit(split_tab_id)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, SplitLayoutTest) { |
| // Create five tabs with two pinned, select the last. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| // Add tab at index 4 to a group. |
| tabstrip()->AddToNewGroup({4}); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ( |
| tabstrip()->GetSplitData(split_tab_id)->visual_data()->split_layout(), |
| split_tabs::SplitTabLayout::kVertical); |
| |
| tabstrip()->UpdateSplitLayout(split_tab_id, |
| split_tabs::SplitTabLayout::kHorizontal); |
| EXPECT_EQ( |
| tabstrip()->GetSplitData(split_tab_id)->visual_data()->split_layout(), |
| split_tabs::SplitTabLayout::kHorizontal); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, SplitRatioTest) { |
| // Create five tabs with two pinned, select the last. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| // Add tab at index 4 to a group. |
| tabstrip()->AddToNewGroup({4}); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ( |
| tabstrip()->GetSplitData(split_tab_id)->visual_data()->split_layout(), |
| split_tabs::SplitTabLayout::kVertical); |
| |
| tabstrip()->UpdateSplitRatio(split_tab_id, 0.7); |
| EXPECT_EQ( |
| tabstrip()->GetSplitData(split_tab_id)->visual_data()->split_ratio(), |
| 0.7); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, ReplaceActiveTabInSplit) { |
| // Create five tabs with two pinned, select the last. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| // Add tab at index 4 to a group. |
| tabstrip()->AddToNewGroup({4}); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip()->AddToNewSplit({3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->UpdateTabInSplit(tabstrip()->GetTabAtIndex(0), 3, |
| TabStripModel::SplitUpdateType::kReplace); |
| EXPECT_EQ("2ps 3ps 1p 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, SwapActiveTabInSplit) { |
| // Create five tabs with two pinned, select the last. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip()->AddToNewSplit({3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->UpdateTabInSplit(tabstrip()->GetTabAtIndex(0), 3, |
| TabStripModel::SplitUpdateType::kSwap); |
| EXPECT_EQ("2ps 3ps 1p 0 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, SwapActiveTabInSplitWithOnlyTabInGroup) { |
| // Create five tabs with two pinned, select the last. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip()->AddToNewSplit({3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| |
| tab_groups::TabGroupId update_group_id = tabstrip()->AddToNewGroup({3}); |
| |
| tabstrip()->UpdateTabInSplit(tabstrip()->GetTabAtIndex(0), 3, |
| TabStripModel::SplitUpdateType::kSwap); |
| EXPECT_EQ("2ps 3ps 1p 0 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(3), update_group_id); |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, ReverseTabsInSplit) { |
| // Create five tabs with two pinned, select the last. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 2, {2})); |
| |
| // Add tab at index 4 to a group. |
| tabstrip()->AddToNewGroup({4}); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0ps 3ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| std::vector<tabs::TabInterface*> old_tabs = |
| tabstrip()->GetSplitData(split_tab_id)->ListTabs(); |
| EXPECT_EQ(2ul, old_tabs.size()); |
| |
| tabstrip()->ReverseTabsInSplit(split_tab_id); |
| |
| EXPECT_EQ("3ps 0ps 1p 2 4", GetTabStripStateString(tabstrip())); |
| std::vector<tabs::TabInterface*> new_tabs = |
| tabstrip()->GetSplitData(split_tab_id)->ListTabs(); |
| EXPECT_EQ(2ul, new_tabs.size()); |
| EXPECT_EQ(old_tabs[1], new_tabs[0]); |
| EXPECT_EQ(old_tabs[0], new_tabs[1]); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, ReverseAndReplaceTabsInSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {1})); |
| |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| EXPECT_EQ("0 1s 2s", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->ReverseTabsInSplit(split_tab_id); |
| EXPECT_EQ("0 2s 1s", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->UpdateTabInSplit(tabstrip()->GetTabAtIndex(1), 0, |
| TabStripModel::SplitUpdateType::kReplace); |
| EXPECT_EQ("0s 1s", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests IsContextMenuCommandEnabled and ExecuteContextMenuCommand with |
| // CommandToggleGrouped. |
| TEST_F(TabStripModelTest, CommandToggleGrouped) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| // Create three tabs, select the first two, and add the first to a group. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {0, 1})); |
| tab_groups::TabGroupId original_group = tabstrip()->AddToNewGroup({0}); |
| ASSERT_TRUE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandToggleGrouped)); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandToggleGrouped)); |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandToggleGrouped)); |
| |
| // Execute CommandToggleGrouped once. Expect both tabs to be in a new group, |
| // since they weren't already in the same group. |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandToggleGrouped); |
| EXPECT_TRUE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_NE(tabstrip()->GetTabGroupForTab(0), original_group); |
| |
| // Only the active tab will remain selected when adding multiple tabs to a |
| // group; all selected tabs continue to be selected when removing multiple |
| // tabs from a group. |
| tabstrip()->SelectTabAt(1); |
| |
| // Execute CommandToggleGrouped again. Expect both tabs to be ungrouped, since |
| // they were in the same group. |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandToggleGrouped); |
| EXPECT_FALSE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| EXPECT_FALSE(tabstrip()->GetTabGroupForTab(1).has_value()); |
| |
| // Execute CommandToggleGrouped again. Expect both tabs to be grouped again. |
| tabstrip()->ExecuteContextMenuCommand(0, TabStripModel::CommandToggleGrouped); |
| EXPECT_TRUE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), tabstrip()->GetTabGroupForTab(1)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests the following context menu commands: |
| // - Close Tab |
| // - Close Other Tabs |
| // - Close Tabs To Right |
| TEST_F(TabStripModelTest, TestContextMenuCloseCommands) { |
| std::unique_ptr<WebContents> opener = CreateWebContents(); |
| WebContents* raw_opener = opener.get(); |
| tabstrip()->AppendWebContents(std::move(opener), true); |
| |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| std::unique_ptr<WebContents> contents2 = CreateWebContents(); |
| std::unique_ptr<WebContents> contents3 = CreateWebContents(); |
| |
| InsertWebContentses(tabstrip(), std::move(contents1), std::move(contents2), |
| std::move(contents3)); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| tabstrip()->ExecuteContextMenuCommand(2, TabStripModel::CommandCloseTab); |
| EXPECT_EQ(3, tabstrip()->count()); |
| |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandCloseTabsToRight); |
| EXPECT_EQ(1, tabstrip()->count()); |
| EXPECT_EQ(raw_opener, tabstrip()->GetActiveWebContents()); |
| |
| std::unique_ptr<WebContents> dummy = CreateWebContents(); |
| WebContents* raw_dummy = dummy.get(); |
| tabstrip()->AppendWebContents(std::move(dummy), false); |
| |
| contents1 = CreateWebContents(); |
| contents2 = CreateWebContents(); |
| contents3 = CreateWebContents(); |
| InsertWebContentses(tabstrip(), std::move(contents1), std::move(contents2), |
| std::move(contents3)); |
| EXPECT_EQ(5, tabstrip()->count()); |
| |
| int dummy_index = tabstrip()->count() - 1; |
| tabstrip()->ActivateTabAt( |
| dummy_index, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(raw_dummy, tabstrip()->GetActiveWebContents()); |
| |
| tabstrip()->ExecuteContextMenuCommand(dummy_index, |
| TabStripModel::CommandCloseOtherTabs); |
| EXPECT_EQ(1, tabstrip()->count()); |
| EXPECT_EQ(raw_dummy, tabstrip()->GetActiveWebContents()); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| TEST_F(TabStripModelTest, GetIndicesClosedByCommand) { |
| const auto indicesClosedAsString = |
| [this](int index, TabStripModel::ContextMenuCommand id) { |
| std::vector<int> indices = |
| tabstrip()->GetIndicesClosedByCommand(index, id); |
| std::string result; |
| for (size_t i = 0; i < indices.size(); ++i) { |
| if (i != 0) { |
| result += " "; |
| } |
| result += base::NumberToString(indices[i]); |
| } |
| return result; |
| }; |
| |
| for (int i = 0; i < 5; ++i) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| } |
| |
| EXPECT_EQ("4 3 2 1", |
| indicesClosedAsString(0, TabStripModel::CommandCloseTabsToRight)); |
| EXPECT_EQ("4 3 2", |
| indicesClosedAsString(1, TabStripModel::CommandCloseTabsToRight)); |
| |
| EXPECT_EQ("4 3 2 1", |
| indicesClosedAsString(0, TabStripModel::CommandCloseOtherTabs)); |
| EXPECT_EQ("4 3 2 0", |
| indicesClosedAsString(1, TabStripModel::CommandCloseOtherTabs)); |
| |
| // Pin the first two tabs. Pinned tabs shouldn't be closed by the close other |
| // commands. |
| tabstrip()->SetTabPinned(0, true); |
| tabstrip()->SetTabPinned(1, true); |
| |
| EXPECT_EQ("4 3 2", |
| indicesClosedAsString(0, TabStripModel::CommandCloseTabsToRight)); |
| EXPECT_EQ("4 3", |
| indicesClosedAsString(2, TabStripModel::CommandCloseTabsToRight)); |
| |
| EXPECT_EQ("4 3 2", |
| indicesClosedAsString(0, TabStripModel::CommandCloseOtherTabs)); |
| EXPECT_EQ("4 3", |
| indicesClosedAsString(2, TabStripModel::CommandCloseOtherTabs)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests whether or not WebContentses are inserted in the correct position |
| // using this "smart" function with a simulated middle click action on a series |
| // of links on the home page. |
| TEST_F(TabStripModelTest, AddWebContents_MiddleClickLinksAndClose) { |
| |
| // Open the Home Page. |
| std::unique_ptr<WebContents> homepage_contents = CreateWebContents(); |
| WebContents* raw_homepage_contents = homepage_contents.get(); |
| tabstrip()->AddWebContents(std::move(homepage_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Open some other tab, by user typing. |
| std::unique_ptr<WebContents> typed_page_contents = CreateWebContents(); |
| WebContents* raw_typed_page_contents = typed_page_contents.get(); |
| tabstrip()->AddWebContents(std::move(typed_page_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(2, tabstrip()->count()); |
| |
| // Re-select the home page. |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Open a bunch of tabs by simulating middle clicking on links on the home |
| // page. |
| std::unique_ptr<WebContents> middle_click_contents1 = CreateWebContents(); |
| WebContents* raw_middle_click_contents1 = middle_click_contents1.get(); |
| tabstrip()->AddWebContents(std::move(middle_click_contents1), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| std::unique_ptr<WebContents> middle_click_contents2 = CreateWebContents(); |
| WebContents* raw_middle_click_contents2 = middle_click_contents2.get(); |
| tabstrip()->AddWebContents(std::move(middle_click_contents2), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| std::unique_ptr<WebContents> middle_click_contents3 = CreateWebContents(); |
| WebContents* raw_middle_click_contents3 = middle_click_contents3.get(); |
| tabstrip()->AddWebContents(std::move(middle_click_contents3), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| EXPECT_EQ(5, tabstrip()->count()); |
| |
| EXPECT_EQ(raw_homepage_contents, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_middle_click_contents1, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_middle_click_contents2, tabstrip()->GetWebContentsAt(2)); |
| EXPECT_EQ(raw_middle_click_contents3, tabstrip()->GetWebContentsAt(3)); |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetWebContentsAt(4)); |
| |
| // Select a tab in the middle of the tabs opened from the home page and start |
| // closing them. Each WebContents should be closed, right to left. This test |
| // is constructed to start at the middle WebContents in the tree to make sure |
| // the cursor wraps around to the first WebContents in the tree before |
| // closing the opener or any other WebContents. |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_middle_click_contents3, tabstrip()->GetActiveWebContents()); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_middle_click_contents1, tabstrip()->GetActiveWebContents()); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_homepage_contents, tabstrip()->GetActiveWebContents()); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetActiveWebContents()); |
| |
| EXPECT_EQ(1, tabstrip()->count()); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests whether or not a WebContents created by a left click on a link |
| // that opens a new tab is inserted correctly adjacent to the tab that spawned |
| // it. |
| TEST_F(TabStripModelTest, AddWebContents_LeftClickPopup) { |
| |
| // Open the Home Page. |
| std::unique_ptr<WebContents> homepage_contents = CreateWebContents(); |
| WebContents* raw_homepage_contents = homepage_contents.get(); |
| tabstrip()->AddWebContents(std::move(homepage_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Open some other tab, by user typing. |
| std::unique_ptr<WebContents> typed_page_contents = CreateWebContents(); |
| WebContents* raw_typed_page_contents = typed_page_contents.get(); |
| tabstrip()->AddWebContents(std::move(typed_page_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(2, tabstrip()->count()); |
| |
| // Re-select the home page. |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Open a tab by simulating a left click on a link that opens in a new tab. |
| std::unique_ptr<WebContents> left_click_contents = CreateWebContents(); |
| WebContents* raw_left_click_contents = left_click_contents.get(); |
| tabstrip()->AddWebContents(std::move(left_click_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_ACTIVE); |
| |
| // Verify the state meets our expectations. |
| EXPECT_EQ(3, tabstrip()->count()); |
| EXPECT_EQ(raw_homepage_contents, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_left_click_contents, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetWebContentsAt(2)); |
| |
| // The newly created tab should be selected. |
| EXPECT_EQ(raw_left_click_contents, tabstrip()->GetActiveWebContents()); |
| |
| // After closing the selected tab, the selection should move to the left, to |
| // the opener. |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_homepage_contents, tabstrip()->GetActiveWebContents()); |
| |
| EXPECT_EQ(2, tabstrip()->count()); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests whether or not new tabs that should split context (typed pages, |
| // generated urls, also blank tabs) open at the end of the tabstrip instead of |
| // in the middle. |
| TEST_F(TabStripModelTest, AddWebContents_CreateNewBlankTab) { |
| |
| // Open the Home Page. |
| std::unique_ptr<WebContents> homepage_contents = CreateWebContents(); |
| WebContents* raw_homepage_contents = homepage_contents.get(); |
| tabstrip()->AddWebContents(std::move(homepage_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Open some other tab, by user typing. |
| std::unique_ptr<WebContents> typed_page_contents = CreateWebContents(); |
| WebContents* raw_typed_page_contents = typed_page_contents.get(); |
| tabstrip()->AddWebContents(std::move(typed_page_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(2, tabstrip()->count()); |
| |
| // Re-select the home page. |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Open a new blank tab in the foreground. |
| std::unique_ptr<WebContents> new_blank_contents = CreateWebContents(); |
| WebContents* raw_new_blank_contents = new_blank_contents.get(); |
| tabstrip()->AddWebContents(std::move(new_blank_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Verify the state of the tabstrip()-> |
| EXPECT_EQ(3, tabstrip()->count()); |
| EXPECT_EQ(raw_homepage_contents, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_new_blank_contents, tabstrip()->GetWebContentsAt(2)); |
| |
| // Now open a couple more blank tabs in the background. |
| std::unique_ptr<WebContents> background_blank_contents1 = CreateWebContents(); |
| WebContents* raw_background_blank_contents1 = |
| background_blank_contents1.get(); |
| tabstrip()->AddWebContents(std::move(background_blank_contents1), -1, |
| ui::PAGE_TRANSITION_TYPED, AddTabTypes::ADD_NONE); |
| std::unique_ptr<WebContents> background_blank_contents2 = CreateWebContents(); |
| WebContents* raw_background_blank_contents2 = |
| background_blank_contents2.get(); |
| tabstrip()->AddWebContents(std::move(background_blank_contents2), -1, |
| ui::PAGE_TRANSITION_GENERATED, |
| AddTabTypes::ADD_NONE); |
| EXPECT_EQ(5, tabstrip()->count()); |
| EXPECT_EQ(raw_homepage_contents, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_new_blank_contents, tabstrip()->GetWebContentsAt(2)); |
| EXPECT_EQ(raw_background_blank_contents1, tabstrip()->GetWebContentsAt(3)); |
| EXPECT_EQ(raw_background_blank_contents2, tabstrip()->GetWebContentsAt(4)); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests whether opener state is correctly forgotten when the user switches |
| // context. |
| TEST_F(TabStripModelTest, AddWebContents_ForgetOpeners) { |
| |
| // Open the home page. |
| std::unique_ptr<WebContents> homepage_contents = CreateWebContents(); |
| WebContents* raw_homepage_contents = homepage_contents.get(); |
| tabstrip()->AddWebContents(std::move(homepage_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Open a blank new tab. |
| std::unique_ptr<WebContents> typed_page_contents = CreateWebContents(); |
| WebContents* raw_typed_page_contents = typed_page_contents.get(); |
| tabstrip()->AddWebContents(std::move(typed_page_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(2, tabstrip()->count()); |
| |
| // Re-select the first tab (home page). |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Open a bunch of tabs by simulating middle clicking on links on the home |
| // page. |
| std::unique_ptr<WebContents> middle_click_contents1 = CreateWebContents(); |
| WebContents* raw_middle_click_contents1 = middle_click_contents1.get(); |
| tabstrip()->AddWebContents(std::move(middle_click_contents1), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| std::unique_ptr<WebContents> middle_click_contents2 = CreateWebContents(); |
| WebContents* raw_middle_click_contents2 = middle_click_contents2.get(); |
| tabstrip()->AddWebContents(std::move(middle_click_contents2), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| std::unique_ptr<WebContents> middle_click_contents3 = CreateWebContents(); |
| WebContents* raw_middle_click_contents3 = middle_click_contents3.get(); |
| tabstrip()->AddWebContents(std::move(middle_click_contents3), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| // Break out of the context by selecting a tab in a different context. |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetWebContentsAt(4)); |
| tabstrip()->SelectLastTab(); |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Step back into the context by selecting a tab inside it. |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(raw_middle_click_contents2, tabstrip()->GetActiveWebContents()); |
| |
| // Now test that closing tabs selects to the right until there are no more, |
| // then to the left, as if there were no context (context has been |
| // successfully forgotten). |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_middle_click_contents3, tabstrip()->GetActiveWebContents()); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_typed_page_contents, tabstrip()->GetActiveWebContents()); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_middle_click_contents1, tabstrip()->GetActiveWebContents()); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(raw_homepage_contents, tabstrip()->GetActiveWebContents()); |
| |
| EXPECT_EQ(1, tabstrip()->count()); |
| |
| tabstrip()->CloseAllTabs(); |
| EXPECT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests whether or not a WebContents in a new tab belongs in the same tab |
| // group as its opener. |
| TEST_F(TabStripModelTest, AddWebContents_LinkOpensInSameGroupAsOpener) { |
| |
| // Open the home page and add the tab to a group. |
| std::unique_ptr<WebContents> homepage_contents = CreateWebContents(); |
| tabstrip()->AddWebContents(std::move(homepage_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| ASSERT_EQ(1, tabstrip()->count()); |
| tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({0}); |
| ASSERT_EQ(tabstrip()->GetTabGroupForTab(0), group_id); |
| ASSERT_EQ(observer()->group_update(group_id).contents_update_count, 1); |
| |
| // Open a tab by simulating a link that opens in a new tab. |
| std::unique_ptr<WebContents> contents = CreateWebContents(); |
| tabstrip()->AddWebContents(std::move(contents), -1, ui::PAGE_TRANSITION_LINK, |
| AddTabTypes::ADD_ACTIVE); |
| EXPECT_EQ(2, tabstrip()->count()); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), group_id); |
| |
| // There should have been a separate notification for the tab being grouped. |
| EXPECT_EQ(observer()->group_update(group_id).contents_update_count, 2); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| |
| // Tests that a inserting a new ungrouped tab between two tabs in the same group |
| // will add that new tab to the group. |
| TEST_F(TabStripModelTest, AddWebContents_UngroupedTabDoesNotBreakContinuity) { |
| |
| // Open two tabs and add them to a tab group. |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| tabstrip()->AddWebContents(std::move(contents1), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| std::unique_ptr<WebContents> contents2 = CreateWebContents(); |
| tabstrip()->AddWebContents(std::move(contents2), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| ASSERT_EQ(2, tabstrip()->count()); |
| tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({0, 1}); |
| ASSERT_EQ(tabstrip()->GetTabGroupForTab(0), group_id); |
| ASSERT_EQ(tabstrip()->GetTabGroupForTab(1), group_id); |
| |
| // Open a new tab between the two tabs in a group and ensure the new tab is |
| // also in the group. |
| std::unique_ptr<WebContents> contents = CreateWebContents(); |
| WebContents* raw_contents = contents.get(); |
| tabstrip()->AddWebContents( |
| std::move(contents), 1, ui::PAGE_TRANSITION_FIRST, |
| AddTabTypes::ADD_ACTIVE | AddTabTypes::ADD_FORCE_INDEX); |
| EXPECT_EQ(3, tabstrip()->count()); |
| ASSERT_EQ(1, tabstrip()->GetIndexOfWebContents(raw_contents)); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), group_id); |
| |
| tabstrip()->CloseAllTabs(); |
| ASSERT_TRUE(tabstrip()->empty()); |
| } |
| // Added for http://b/issue?id=958960 |
| TEST_F(TabStripModelTest, AppendContentsReselectionTest) { |
| |
| // Open the Home Page. |
| tabstrip()->AddWebContents(CreateWebContents(), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Open some other tab, by user typing. |
| tabstrip()->AddWebContents(CreateWebContents(), -1, ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_NONE); |
| |
| // The selected tab should still be the first. |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| // Now simulate a link click that opens a new tab (by virtue of target=_blank) |
| // and make sure the correct tab gets selected when the new tab is closed. |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| EXPECT_EQ(2, tabstrip()->active_index()); |
| int previous_tab_count = tabstrip()->count(); |
| tabstrip()->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(previous_tab_count - 1, tabstrip()->count()); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| |
| // Clean up after ourselves. |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // Added for http://b/issue?id=1027661 |
| TEST_F(TabStripModelTest, ReselectionConsidersChildrenTest) { |
| |
| // Open page A |
| std::unique_ptr<WebContents> page_a_contents = CreateWebContents(); |
| WebContents* raw_page_a_contents = page_a_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_a_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Simulate middle click to open page A.A and A.B |
| std::unique_ptr<WebContents> page_a_a_contents = CreateWebContents(); |
| WebContents* raw_page_a_a_contents = page_a_a_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_a_a_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| std::unique_ptr<WebContents> page_a_b_contents = CreateWebContents(); |
| WebContents* raw_page_a_b_contents = page_a_b_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_a_b_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| // Select page A.A |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(raw_page_a_a_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Simulate a middle click to open page A.A.A |
| std::unique_ptr<WebContents> page_a_a_a_contents = CreateWebContents(); |
| WebContents* raw_page_a_a_a_contents = page_a_a_a_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_a_a_a_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| EXPECT_EQ(raw_page_a_a_a_contents, tabstrip()->GetWebContentsAt(2)); |
| |
| // Close page A.A |
| tabstrip()->CloseWebContentsAt(tabstrip()->active_index(), |
| TabCloseTypes::CLOSE_NONE); |
| |
| // Page A.A.A should be selected, NOT A.B |
| EXPECT_EQ(raw_page_a_a_a_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Close page A.A.A |
| tabstrip()->CloseWebContentsAt(tabstrip()->active_index(), |
| TabCloseTypes::CLOSE_NONE); |
| |
| // Page A.B should be selected |
| EXPECT_EQ(raw_page_a_b_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Close page A.B |
| tabstrip()->CloseWebContentsAt(tabstrip()->active_index(), |
| TabCloseTypes::CLOSE_NONE); |
| |
| // Page A should be selected |
| EXPECT_EQ(raw_page_a_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Clean up. |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, NewTabAtEndOfStripInheritsOpener) { |
| // Open page A |
| tabstrip()->AddWebContents(CreateWebContents(), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Open pages B, C and D in the background from links on page A... |
| for (int i = 0; i < 3; ++i) { |
| tabstrip()->AddWebContents(CreateWebContents(), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| } |
| |
| // Switch to page B's tab. |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Open a New Tab at the end of the strip (simulate Ctrl+T) |
| std::unique_ptr<WebContents> new_contents = CreateWebContents(); |
| WebContents* raw_new_contents = new_contents.get(); |
| tabstrip()->AddWebContents(std::move(new_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(4, tabstrip()->GetIndexOfWebContents(raw_new_contents)); |
| EXPECT_EQ(4, tabstrip()->active_index()); |
| |
| // Close the New Tab that was just opened. We should be returned to page B's |
| // Tab... |
| tabstrip()->CloseWebContentsAt(4, TabCloseTypes::CLOSE_NONE); |
| |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| |
| // Open a non-New Tab tab at the end of the strip, with a TYPED transition. |
| // This is like typing a URL in the address bar and pressing Alt+Enter. The |
| // behavior should be the same as above. |
| std::unique_ptr<WebContents> page_e_contents = CreateWebContents(); |
| WebContents* raw_page_e_contents = page_e_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_e_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(4, tabstrip()->GetIndexOfWebContents(raw_page_e_contents)); |
| EXPECT_EQ(4, tabstrip()->active_index()); |
| |
| // Close the Tab. Selection should shift back to page B's Tab. |
| tabstrip()->CloseWebContentsAt(4, TabCloseTypes::CLOSE_NONE); |
| |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| |
| // Open a non-New Tab tab at the end of the strip, with some other |
| // transition. This is like right clicking on a bookmark and choosing "Open |
| // in New Tab". No opener relationship should be preserved between this Tab |
| // and the one that was active when the gesture was performed. |
| std::unique_ptr<WebContents> page_f_contents = CreateWebContents(); |
| WebContents* raw_page_f_contents = page_f_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_f_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| AddTabTypes::ADD_ACTIVE); |
| |
| EXPECT_EQ(4, tabstrip()->GetIndexOfWebContents(raw_page_f_contents)); |
| EXPECT_EQ(4, tabstrip()->active_index()); |
| |
| // Close the Tab. The next-adjacent should be selected. |
| tabstrip()->CloseWebContentsAt(4, TabCloseTypes::CLOSE_NONE); |
| |
| EXPECT_EQ(3, tabstrip()->active_index()); |
| |
| // Clean up. |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // A test of navigations in a tab that is part of a tree of openers from some |
| // parent tab. If the navigations are link clicks, the opener relationships of |
| // the tab. If they are of any other type, they are not preserved. |
| TEST_F(TabStripModelTest, NavigationForgetsOpeners) { |
| |
| // Open page A |
| tabstrip()->AddWebContents(CreateWebContents(), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Open pages B, C and D in the background from links on page A... |
| std::unique_ptr<WebContents> page_c_contents = CreateWebContents(); |
| WebContents* raw_page_c_contents = page_c_contents.get(); |
| std::unique_ptr<WebContents> page_d_contents = CreateWebContents(); |
| WebContents* raw_page_d_contents = page_d_contents.get(); |
| tabstrip()->AddWebContents(CreateWebContents(), -1, ui::PAGE_TRANSITION_LINK, |
| AddTabTypes::ADD_NONE); |
| tabstrip()->AddWebContents(std::move(page_c_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| tabstrip()->AddWebContents(std::move(page_d_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| // Open page E in a different opener tree from page A. |
| std::unique_ptr<WebContents> page_e_contents = CreateWebContents(); |
| WebContents* raw_page_e_contents = page_e_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_e_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_NONE); |
| |
| // Tell the TabStripModel that we are navigating page D via a link click. |
| tabstrip()->ActivateTabAt( |
| 3, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->TabNavigating(raw_page_d_contents, ui::PAGE_TRANSITION_LINK); |
| |
| // Close page D, page C should be selected. (part of same opener tree). |
| tabstrip()->CloseWebContentsAt(3, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(2, tabstrip()->active_index()); |
| |
| // Tell the TabStripModel that we are navigating in page C via a bookmark. |
| tabstrip()->TabNavigating(raw_page_c_contents, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK); |
| |
| // Close page C, page E should be selected. (C is no longer part of the |
| // A-B-C-D tree, selection moves to the right). |
| tabstrip()->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(raw_page_e_contents, |
| tabstrip()->GetWebContentsAt(tabstrip()->active_index())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // A test for the "quick look" use case where the user can open a new tab at the |
| // end of the tab strip, do one search, and then close the tab to get back to |
| // where they were. |
| TEST_F(TabStripModelTest, NavigationForgettingDoesntAffectNewTab) { |
| |
| // Open a tab and several tabs from it, then select one of the tabs that was |
| // opened. |
| tabstrip()->AddWebContents(CreateWebContents(), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_ACTIVE); |
| |
| std::unique_ptr<WebContents> page_c_contents = CreateWebContents(); |
| WebContents* raw_page_c_contents = page_c_contents.get(); |
| std::unique_ptr<WebContents> page_d_contents = CreateWebContents(); |
| WebContents* raw_page_d_contents = page_d_contents.get(); |
| tabstrip()->AddWebContents(CreateWebContents(), -1, ui::PAGE_TRANSITION_LINK, |
| AddTabTypes::ADD_NONE); |
| tabstrip()->AddWebContents(std::move(page_c_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| tabstrip()->AddWebContents(std::move(page_d_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // TEST 1: A tab in the middle of a bunch of tabs is active and the user opens |
| // a new tab at the end of the tabstrip()-> Closing that new tab will select |
| // the tab that they were last on. |
| |
| // Open a new tab at the end of the TabStrip. |
| std::unique_ptr<WebContents> new_tab_contents = CreateWebContents(); |
| WebContents* raw_new_tab_contents = new_tab_contents.get(); |
| content::WebContentsTester::For(raw_new_tab_contents) |
| ->NavigateAndCommit(GURL("chrome://newtab")); |
| tabstrip()->AddWebContents(std::move(new_tab_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // The opener should still be remembered after one navigation. |
| content::NavigationSimulator::CreateBrowserInitiated( |
| GURL("http://example.com"), raw_new_tab_contents) |
| ->Start(); |
| tabstrip()->TabNavigating(raw_new_tab_contents, ui::PAGE_TRANSITION_TYPED); |
| |
| // At this point, if we close this tab the last selected one should be |
| // re-selected. |
| tabstrip()->CloseWebContentsAt(tabstrip()->count() - 1, |
| TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(raw_page_c_contents, |
| tabstrip()->GetWebContentsAt(tabstrip()->active_index())); |
| |
| // TEST 2: As above, but the user selects another tab in the strip and thus |
| // that new tab's opener relationship is forgotten. |
| |
| // Open a new tab again. |
| tabstrip()->AddWebContents(CreateWebContents(), -1, ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Now select the first tab. |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Now select the last tab. |
| tabstrip()->ActivateTabAt( |
| tabstrip()->count() - 1, |
| TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Now close the last tab. The next adjacent should be selected. |
| tabstrip()->CloseWebContentsAt(tabstrip()->count() - 1, |
| TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(raw_page_d_contents, |
| tabstrip()->GetWebContentsAt(tabstrip()->active_index())); |
| |
| // TEST 3: As above, but the user does multiple navigations and thus the tab's |
| // opener relationship is forgotten. |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Open a new tab but navigate away from the new tab page. |
| new_tab_contents = CreateWebContents(); |
| raw_new_tab_contents = new_tab_contents.get(); |
| tabstrip()->AddWebContents(std::move(new_tab_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| content::WebContentsTester::For(raw_new_tab_contents) |
| ->NavigateAndCommit(GURL("http://example.org")); |
| |
| // Do another navigation. The opener should be forgotten. |
| content::NavigationSimulator::CreateBrowserInitiated( |
| GURL("http://example.com"), raw_new_tab_contents) |
| ->Start(); |
| tabstrip()->TabNavigating(raw_new_tab_contents, ui::PAGE_TRANSITION_TYPED); |
| |
| // Close the tab. The next adjacent should be selected. |
| tabstrip()->CloseWebContentsAt(tabstrip()->count() - 1, |
| TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(raw_page_d_contents, |
| tabstrip()->GetWebContentsAt(tabstrip()->active_index())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| namespace { |
| |
| class UnloadListenerTabStripModelDelegate : public TestTabStripModelDelegate { |
| public: |
| UnloadListenerTabStripModelDelegate() = default; |
| UnloadListenerTabStripModelDelegate( |
| const UnloadListenerTabStripModelDelegate&) = delete; |
| UnloadListenerTabStripModelDelegate& operator=( |
| const UnloadListenerTabStripModelDelegate&) = delete; |
| ~UnloadListenerTabStripModelDelegate() override = default; |
| |
| void set_run_unload_listener(bool value) { run_unload_ = value; } |
| |
| bool RunUnloadListenerBeforeClosing(WebContents* contents) override { |
| return run_unload_; |
| } |
| |
| private: |
| // Whether to report that we need to run an unload listener before closing. |
| bool run_unload_ = false; |
| }; |
| |
| } // namespace |
| |
| // Tests that fast shutdown is attempted appropriately. |
| TEST_F(TabStripModelTest, FastShutdown) { |
| UnloadListenerTabStripModelDelegate delegate; |
| TabStripModel tabstrip(&delegate, profile()); |
| MockTabStripModelObserver observer; |
| tabstrip.AddObserver(&observer); |
| |
| EXPECT_TRUE(tabstrip.empty()); |
| |
| // Make sure fast shutdown is attempted when tabs that share a RPH are shut |
| // down. |
| { |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| WebContents* raw_contents1 = contents1.get(); |
| std::unique_ptr<WebContents> contents2 = |
| CreateWebContentsWithSharedRPH(contents1.get()); |
| |
| SetID(contents1.get(), 1); |
| SetID(contents2.get(), 2); |
| |
| tabstrip.AppendWebContents(std::move(contents1), true); |
| tabstrip.AppendWebContents(std::move(contents2), true); |
| |
| // Turn on the fake unload listener so the tabs don't actually get shut |
| // down when we call CloseAllTabs()---we need to be able to check that |
| // fast shutdown was attempted. |
| delegate.set_run_unload_listener(true); |
| tabstrip.CloseAllTabs(); |
| // On a mock RPH this checks whether we *attempted* fast shutdown. |
| // A real RPH would reject our attempt since there is an unload handler. |
| EXPECT_TRUE(raw_contents1->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->FastShutdownStarted()); |
| EXPECT_EQ(2, tabstrip.count()); |
| |
| delegate.set_run_unload_listener(false); |
| observer.ClearStates(); |
| tabstrip.CloseAllTabs(); |
| EXPECT_TRUE(tabstrip.empty()); |
| } |
| |
| // Make sure fast shutdown is not attempted when only some tabs that share a |
| // RPH are shut down. |
| { |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| WebContents* raw_contents1 = contents1.get(); |
| std::unique_ptr<WebContents> contents2 = |
| CreateWebContentsWithSharedRPH(contents1.get()); |
| |
| SetID(contents1.get(), 1); |
| SetID(contents2.get(), 2); |
| |
| tabstrip.AppendWebContents(std::move(contents1), true); |
| tabstrip.AppendWebContents(std::move(contents2), true); |
| |
| tabstrip.CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_FALSE(raw_contents1->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->FastShutdownStarted()); |
| EXPECT_EQ(1, tabstrip.count()); |
| |
| tabstrip.CloseAllTabs(); |
| EXPECT_TRUE(tabstrip.empty()); |
| } |
| } |
| |
| // Tests various permutations of pinning tabs. |
| TEST_F(TabStripModelTest, Pinning) { |
| typedef MockTabStripModelObserver::State State; |
| |
| std::unique_ptr<WebContents> contents1 = CreateWebContentsWithID(1); |
| WebContents* raw_contents1 = contents1.get(); |
| std::unique_ptr<WebContents> contents3 = CreateWebContentsWithID(3); |
| WebContents* raw_contents3 = contents3.get(); |
| |
| // Note! The ordering of these tests is important, each subsequent test |
| // builds on the state established in the previous. This is important if you |
| // ever insert tests rather than append. |
| |
| // Initial state, three tabs, first selected. |
| tabstrip()->AppendWebContents(std::move(contents1), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(2), false); |
| tabstrip()->AppendWebContents(std::move(contents3), false); |
| |
| observer()->ClearStates(); |
| |
| // Pin the first tab, this shouldn't visually reorder anything. |
| { |
| EXPECT_EQ(0, tabstrip()->SetTabPinned(0, true)); |
| |
| // As the order didn't change, we should get a pinned notification. |
| ASSERT_EQ(1, observer()->GetStateCount()); |
| State state(raw_contents1, 0, MockTabStripModelObserver::PINNED); |
| observer()->ExpectStateEquals(0, state); |
| |
| // And verify the state. |
| EXPECT_EQ("1p 2 3", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| // Unpin the first tab. |
| { |
| EXPECT_EQ(0, tabstrip()->SetTabPinned(0, false)); |
| |
| // As the order didn't change, we should get a pinned notification. |
| ASSERT_EQ(1, observer()->GetStateCount()); |
| State state(raw_contents1, 0, MockTabStripModelObserver::PINNED); |
| observer()->ExpectStateEquals(0, state); |
| |
| // And verify the state. |
| EXPECT_EQ("1 2 3", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| // Pin the 3rd tab, which should move it to the front. |
| { |
| EXPECT_EQ(0, tabstrip()->SetTabPinned(2, true)); |
| |
| // The pinning should have resulted in a move and a pinned notification. |
| ASSERT_EQ(3, observer()->GetStateCount()); |
| State state(raw_contents3, 0, MockTabStripModelObserver::MOVE); |
| state.src_index = 2; |
| observer()->ExpectStateEquals(0, state); |
| |
| state = State(raw_contents3, 0, MockTabStripModelObserver::PINNED); |
| observer()->ExpectStateEquals(2, state); |
| |
| // And verify the state. |
| EXPECT_EQ("3p 1 2", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| // Pin the tab "1", which shouldn't move anything. |
| { |
| EXPECT_EQ(1, tabstrip()->SetTabPinned(1, true)); |
| |
| // As the order didn't change, we should get a pinned notification. |
| ASSERT_EQ(1, observer()->GetStateCount()); |
| State state(raw_contents1, 1, MockTabStripModelObserver::PINNED); |
| observer()->ExpectStateEquals(0, state); |
| |
| // And verify the state. |
| EXPECT_EQ("3p 1p 2", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| // Try to move tab "2" to the front, it should be ignored. |
| { |
| EXPECT_EQ(2, tabstrip()->MoveWebContentsAt(2, 0, false)); |
| |
| // As the order didn't change, we should get a pinned notification. |
| ASSERT_EQ(0, observer()->GetStateCount()); |
| |
| // And verify the state. |
| EXPECT_EQ("3p 1p 2", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| // Unpin tab "3", which implicitly moves it to the end. |
| { |
| EXPECT_EQ(1, tabstrip()->SetTabPinned(0, false)); |
| |
| ASSERT_EQ(3, observer()->GetStateCount()); |
| State state(raw_contents3, 1, MockTabStripModelObserver::MOVE); |
| state.src_index = 0; |
| observer()->ExpectStateEquals(0, state); |
| |
| state = State(raw_contents3, 1, MockTabStripModelObserver::PINNED); |
| observer()->ExpectStateEquals(2, state); |
| |
| // And verify the state. |
| EXPECT_EQ("1p 3 2", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| // Unpin tab "3", nothing should happen. |
| { |
| EXPECT_EQ(1, tabstrip()->SetTabPinned(1, false)); |
| |
| ASSERT_EQ(0, observer()->GetStateCount()); |
| |
| EXPECT_EQ("1p 3 2", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| // Pin "3" and "1". |
| { |
| EXPECT_EQ(0, tabstrip()->SetTabPinned(0, true)); |
| EXPECT_EQ(1, tabstrip()->SetTabPinned(1, true)); |
| |
| EXPECT_EQ("1p 3p 2", GetTabStripStateString(tabstrip())); |
| |
| observer()->ClearStates(); |
| } |
| |
| std::unique_ptr<WebContents> contents4 = CreateWebContentsWithID(4); |
| WebContents* raw_contents4 = contents4.get(); |
| |
| // Insert "4" between "1" and "3". As "1" and "4" are pinned, "4" should end |
| // up after them. |
| { |
| EXPECT_EQ(2, tabstrip()->InsertWebContentsAt(1, std::move(contents4), |
| AddTabTypes::ADD_NONE)); |
| |
| ASSERT_EQ(1, observer()->GetStateCount()); |
| State state(raw_contents4, 2, MockTabStripModelObserver::INSERT); |
| observer()->ExpectStateEquals(0, state); |
| |
| EXPECT_EQ("1p 3p 4 2", GetTabStripStateString(tabstrip())); |
| } |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // Makes sure the TabStripModel calls the right observer methods during a |
| // replace. |
| TEST_F(TabStripModelTest, ReplaceSendsSelected) { |
| typedef MockTabStripModelObserver::State State; |
| |
| std::unique_ptr<WebContents> first_contents = CreateWebContents(); |
| WebContents* raw_first_contents = first_contents.get(); |
| tabstrip()->AddWebContents(std::move(first_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| observer()->ClearStates(); |
| |
| std::unique_ptr<WebContents> new_contents = CreateWebContents(); |
| WebContents* raw_new_contents = new_contents.get(); |
| tabstrip()->DiscardWebContentsAt(0, std::move(new_contents)); |
| |
| ASSERT_EQ(2, observer()->GetStateCount()); |
| |
| // First event should be for replaced. |
| State state(raw_new_contents, 0, MockTabStripModelObserver::REPLACED); |
| state.src_contents = raw_first_contents; |
| observer()->ExpectStateEquals(0, state); |
| |
| // And the second for selected. |
| state = State(raw_new_contents, 0, MockTabStripModelObserver::ACTIVATE); |
| state.src_contents = raw_first_contents; |
| state.change_reason = TabStripModelObserver::CHANGE_REASON_REPLACED; |
| observer()->ExpectStateEquals(1, state); |
| |
| // Now add another tab and replace it, making sure we don't get a selected |
| // event this time. |
| std::unique_ptr<WebContents> third_contents = CreateWebContents(); |
| WebContents* raw_third_contents = third_contents.get(); |
| tabstrip()->AddWebContents(std::move(third_contents), 1, |
| ui::PAGE_TRANSITION_TYPED, AddTabTypes::ADD_NONE); |
| |
| observer()->ClearStates(); |
| |
| // And replace it. |
| new_contents = CreateWebContents(); |
| raw_new_contents = new_contents.get(); |
| tabstrip()->DiscardWebContentsAt(1, std::move(new_contents)); |
| |
| ASSERT_EQ(1, observer()->GetStateCount()); |
| |
| state = State(raw_new_contents, 1, MockTabStripModelObserver::REPLACED); |
| state.src_contents = raw_third_contents; |
| observer()->ExpectStateEquals(0, state); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // Ensure pinned tabs are not mixed with non-pinned tabs when using |
| // MoveWebContentsAt. |
| TEST_F(TabStripModelTest, MoveWebContentsAtWithPinned) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 3, {0})); |
| EXPECT_EQ("0p 1p 2p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| // Move middle tabs into the wrong area. |
| tabstrip()->MoveWebContentsAt(1, 5, true); |
| EXPECT_EQ("0p 2p 1p 3 4 5", GetTabStripStateString(tabstrip())); |
| tabstrip()->MoveWebContentsAt(4, 1, true); |
| EXPECT_EQ("0p 2p 1p 4 3 5", GetTabStripStateString(tabstrip())); |
| |
| // Test moving edge cases into the wrong area. |
| tabstrip()->MoveWebContentsAt(5, 0, true); |
| EXPECT_EQ("0p 2p 1p 5 4 3", GetTabStripStateString(tabstrip())); |
| tabstrip()->MoveWebContentsAt(0, 5, true); |
| EXPECT_EQ("2p 1p 0p 5 4 3", GetTabStripStateString(tabstrip())); |
| |
| // Test moving edge cases in the correct area. |
| tabstrip()->MoveWebContentsAt(3, 5, true); |
| EXPECT_EQ("2p 1p 0p 4 3 5", GetTabStripStateString(tabstrip())); |
| tabstrip()->MoveWebContentsAt(2, 0, true); |
| EXPECT_EQ("0p 2p 1p 4 3 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabNext) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 3, {3})); |
| EXPECT_EQ("0p 1p 2p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("0p 1p 2p 4 3 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("0p 1p 2p 4 5 3", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("0p 1p 2p 4 5 3", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabNext_Pinned) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 3, {0})); |
| EXPECT_EQ("0p 1p 2p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1p 0p 2p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1p 2p 0p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1p 2p 0p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabNext_Group) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 0, {0})); |
| EXPECT_EQ("0 1 2 3", GetTabStripStateString(tabstrip())); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({1, 2}); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), std::nullopt); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("0 1 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), group); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1 0 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), group); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1 2 0 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(2), group); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1 2 0 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(2), std::nullopt); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabNext_GroupAtEnd) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 2, 0, {0})); |
| EXPECT_EQ("0 1", GetTabStripStateString(tabstrip())); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0, 1}); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1 0", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), group); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1 0", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), std::nullopt); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabNext_PinnedDoesNotGroup) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 1, {0})); |
| EXPECT_EQ("0p 1 2 3", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->AddToNewGroup({1, 2}); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), std::nullopt); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("0p 1 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), std::nullopt); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabNext_Split) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {1})); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(3); |
| tabstrip()->AddToNewSplit({4}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->AddToNewGroup({3, 4}); |
| ASSERT_EQ("0 1s 2s 3g0s 4g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1s 2s 0 3g0s 4g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1s 2s 0g0 3g0s 4g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1s 2s 3g0s 4g0s 0g0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1s 2s 3g0s 4g0s 0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1s 2s 3g0s 4g0s 0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1g0s 2g0s 3g0s 4g0s 0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("3g0s 4g0s 1g0s 2g0s 0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("3g0s 4g0s 1s 2s 0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("3g0s 4g0s 0 1s 2s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("3g0s 4g0s 0 1s 2s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabNext_SplitPinned) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 3, {0})); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0p 1ps 2ps", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1ps 2ps 0p", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("1ps 2ps 0p", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("0p 1ps 2ps", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabNext(); |
| EXPECT_EQ("0p 1ps 2ps", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabPrevious) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 3, {5})); |
| EXPECT_EQ("0p 1p 2p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0p 1p 2p 3 5 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0p 1p 2p 5 3 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0p 1p 2p 5 3 4", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabPrevious_Pinned) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 3, {2})); |
| EXPECT_EQ("0p 1p 2p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0p 2p 1p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("2p 0p 1p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("2p 0p 1p 3 4 5", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabPrevious_Group) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 0, {3})); |
| EXPECT_EQ("0 1 2 3", GetTabStripStateString(tabstrip())); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({1, 2}); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(3), std::nullopt); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0 1 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(3), group); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0 1 3 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(2), group); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0 3 1 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), group); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0 3 1 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), std::nullopt); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabPrevious_GroupAtEnd) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 2, 0, {1})); |
| EXPECT_EQ("0 1", GetTabStripStateString(tabstrip())); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0, 1}); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("1 0", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), group); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("1 0", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), std::nullopt); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabPrevious_Split) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {0})); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->AddToNewGroup({0, 1}); |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->AddToNewSplit({3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0g0s 1g0s 2s 3s 4", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(4); |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0g0s 1g0s 4 2s 3s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0g0s 1g0s 4g0 2s 3s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("4g0 0g0s 1g0s 2s 3s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("4 0g0s 1g0s 2s 3s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("4 0g0s 1g0s 2s 3s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(4); |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("4 0g0s 1g0s 2g0s 3g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("4 2g0s 3g0s 0g0s 1g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("4 2s 3s 0g0s 1g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("2s 3s 4 0g0s 1g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("2s 3s 4 0g0s 1g0s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveTabPrevious_SplitPinned) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 3, 3, {0})); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0ps 1ps 2p", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("2p 0ps 1ps", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("2p 0ps 1ps", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0ps 1ps 2p", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->MoveTabPrevious(); |
| EXPECT_EQ("0ps 1ps 2p", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveSelectedTabsTo) { |
| struct TestData { |
| // Number of tabs the tab strip should have. |
| const int tab_count; |
| |
| // Number of pinned tabs. |
| const int pinned_count; |
| |
| // Index of the tabs to select. |
| const std::vector<int> selected_tabs; |
| |
| // Index to move the tabs to. |
| const int target_index; |
| |
| // Expected state after the move (space separated list of indices). |
| const std::string state_after_move; |
| }; |
| auto test_data = std::to_array<TestData>({ |
| // 1 selected tab. |
| {2, 0, {0}, 1, "1 0"}, |
| {3, 0, {0}, 2, "1 2 0"}, |
| {3, 0, {2}, 0, "2 0 1"}, |
| {3, 0, {2}, 1, "0 2 1"}, |
| {3, 0, {0, 1}, 0, "0 1 2"}, |
| |
| // 2 selected tabs. |
| {6, 0, {4, 5}, 1, "0 4 5 1 2 3"}, |
| {3, 0, {0, 1}, 1, "2 0 1"}, |
| {4, 0, {0, 2}, 1, "1 0 2 3"}, |
| {6, 0, {0, 1}, 3, "2 3 4 0 1 5"}, |
| |
| // 3 selected tabs. |
| {6, 0, {0, 2, 3}, 3, "1 4 5 0 2 3"}, |
| {7, 0, {4, 5, 6}, 1, "0 4 5 6 1 2 3"}, |
| {7, 0, {1, 5, 6}, 4, "0 2 3 4 1 5 6"}, |
| |
| // 5 selected tabs. |
| {8, 0, {0, 2, 3, 6, 7}, 3, "1 4 5 0 2 3 6 7"}, |
| |
| // 7 selected tabs |
| {16, |
| 0, |
| {0, 1, 2, 3, 4, 7, 9}, |
| 8, |
| "5 6 8 10 11 12 13 14 0 1 2 3 4 7 9 15"}, |
| |
| // With pinned tabs. |
| {6, 2, {2, 3}, 2, "0p 1p 2 3 4 5"}, |
| {6, 2, {0, 4}, 3, "1p 0p 2 3 4 5"}, |
| {6, 3, {1, 2, 4}, 0, "1p 2p 0p 4 3 5"}, |
| {8, 3, {1, 3, 4}, 4, "0p 2p 1p 5 6 3 4 7"}, |
| |
| {7, 4, {2, 3, 4}, 3, "0p 1p 2p 3p 5 4 6"}, |
| }); |
| |
| for (size_t i = 0; i < std::size(test_data); ++i) { |
| ASSERT_NO_FATAL_FAILURE(PrepareTabstripForSelectionTest( |
| tabstrip(), test_data[i].tab_count, test_data[i].pinned_count, |
| test_data[i].selected_tabs)); |
| tabstrip()->MoveSelectedTabsTo(test_data[i].target_index, std::nullopt); |
| EXPECT_EQ(test_data[i].state_after_move, GetTabStripStateString(tabstrip())) |
| << i; |
| tabstrip()->CloseAllTabs(); |
| } |
| } |
| |
| TEST_F(TabStripModelTest, MoveSelectedTabsToWithEntireGroupSelected) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 10, 5, {2, 3, 6, 7})); |
| |
| tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({6, 7}); |
| tabstrip()->SelectTabAt(6); |
| tabstrip()->SelectTabAt(7); |
| tabstrip()->SelectTabAt(9); |
| |
| tabstrip()->MoveSelectedTabsTo(3, std::nullopt); |
| EXPECT_EQ("0p 1p 4p 2p 3p 6 7 9 5 8", GetTabStripStateString(tabstrip())); |
| EXPECT_TRUE(tabstrip()->group_model()->ContainsTabGroup(group_id)); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveSelectedTabsToWithPartGroupSelected) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 10, 5, {2, 3})); |
| |
| tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({6, 7}); |
| tabstrip()->SelectTabAt(6); |
| |
| tabstrip()->MoveSelectedTabsTo(3, std::nullopt); |
| EXPECT_EQ("0p 1p 4p 2p 3p 6 5 7 8 9", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group_id)->ListTabs().length(), |
| 1u); |
| } |
| |
| TEST_F(TabStripModelTest, MoveSelectedTabsToWithSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 10, 5, {2, 3, 6, 7})); |
| |
| tabstrip()->ActivateTabAt( |
| 6, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip()->AddToNewSplit({7}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| PrepareTabstripForSelectionTest(tabstrip(), 0, 0, {6, 7}); |
| |
| tabstrip()->MoveSelectedTabsTo(3, std::nullopt); |
| EXPECT_EQ("0p 1p 2p 3p 4p 6s 7s 5 8 9", GetTabStripStateString(tabstrip())); |
| } |
| |
| TEST_F(TabStripModelTest, MoveSelectedTabsToWithGroupAndSplit) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 13, 5, {2, 3})); |
| |
| tab_groups::TabGroupId group_id_one = tabstrip()->AddToNewGroup({6, 7, 8}); |
| tab_groups::TabGroupId group_id_two = tabstrip()->AddToNewGroup({10}); |
| |
| // Create splits in group, ungrouped and pinned states. |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->AddToNewSplit({0}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| tabstrip()->ActivateTabAt( |
| 6, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->AddToNewSplit({7}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| tabstrip()->ActivateTabAt( |
| 11, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->AddToNewSplit({12}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // Move all the split tabs along with some other tabs. |
| PrepareTabstripForSelectionTest(tabstrip(), 0, 0, |
| {0, 1, 6, 7, 8, 10, 11, 12}); |
| |
| tabstrip()->MoveSelectedTabsTo(3, std::nullopt); |
| EXPECT_EQ("2p 3p 4p 0ps 1ps 6s 7s 8 10 11s 12s 5 9", |
| GetTabStripStateString(tabstrip())); |
| EXPECT_TRUE(tabstrip()->group_model()->ContainsTabGroup(group_id_one)); |
| EXPECT_TRUE(tabstrip()->group_model()->ContainsTabGroup(group_id_two)); |
| } |
| |
| // Tests that moving a tab forgets all openers referencing it. |
| TEST_F(TabStripModelTest, MoveSelectedTabsTo_ForgetOpeners) { |
| |
| // Open page A as a new tab and then A1 in the background from A. |
| std::unique_ptr<WebContents> page_a_contents = CreateWebContents(); |
| WebContents* raw_page_a_contents = page_a_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_a_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_ACTIVE); |
| std::unique_ptr<WebContents> page_a1_contents = CreateWebContents(); |
| WebContents* raw_page_a1_contents = page_a1_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_a1_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| // Likewise, open pages B and B1. |
| std::unique_ptr<WebContents> page_b_contents = CreateWebContents(); |
| WebContents* raw_page_b_contents = page_b_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_b_contents), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_ACTIVE); |
| std::unique_ptr<WebContents> page_b1_contents = CreateWebContents(); |
| WebContents* raw_page_b1_contents = page_b1_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_b1_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| EXPECT_EQ(raw_page_a_contents, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_page_a1_contents, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_page_b_contents, tabstrip()->GetWebContentsAt(2)); |
| EXPECT_EQ(raw_page_b1_contents, tabstrip()->GetWebContentsAt(3)); |
| |
| // Move page B to the start of the tab tabstrip()-> |
| tabstrip()->MoveSelectedTabsTo(0, std::nullopt); |
| |
| // Open page B2 in the background from B. It should end up after B. |
| std::unique_ptr<WebContents> page_b2_contents = CreateWebContents(); |
| WebContents* raw_page_b2_contents = page_b2_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_b2_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| EXPECT_EQ(raw_page_b_contents, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_page_b2_contents, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_page_a_contents, tabstrip()->GetWebContentsAt(2)); |
| EXPECT_EQ(raw_page_a1_contents, tabstrip()->GetWebContentsAt(3)); |
| EXPECT_EQ(raw_page_b1_contents, tabstrip()->GetWebContentsAt(4)); |
| |
| // Switch to A. |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(raw_page_a_contents, tabstrip()->GetActiveWebContents()); |
| |
| // Open page A2 in the background from A. It should end up after A1. |
| std::unique_ptr<WebContents> page_a2_contents = CreateWebContents(); |
| WebContents* raw_page_a2_contents = page_a2_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_a2_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| EXPECT_EQ(raw_page_b_contents, tabstrip()->GetWebContentsAt(0)); |
| EXPECT_EQ(raw_page_b2_contents, tabstrip()->GetWebContentsAt(1)); |
| EXPECT_EQ(raw_page_a_contents, tabstrip()->GetWebContentsAt(2)); |
| EXPECT_EQ(raw_page_a1_contents, tabstrip()->GetWebContentsAt(3)); |
| EXPECT_EQ(raw_page_a2_contents, tabstrip()->GetWebContentsAt(4)); |
| EXPECT_EQ(raw_page_b1_contents, tabstrip()->GetWebContentsAt(5)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, CloseSelectedTabs) { |
| for (int i = 0; i < 3; ++i) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| } |
| tabstrip()->SelectTabAt(1); |
| tabstrip()->CloseSelectedTabs(); |
| EXPECT_EQ(1, tabstrip()->count()); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, FirstTabIsActive) { |
| // Add the first tab as a background tab. |
| tabstrip()->AppendWebContents(CreateWebContents(), /*foreground=*/false); |
| |
| // The tab is still added as active, because it is the first tab. |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| } |
| |
| TEST_F(TabStripModelTest, MultipleSelection) { |
| typedef MockTabStripModelObserver::State State; |
| |
| std::unique_ptr<WebContents> contents0 = CreateWebContents(); |
| WebContents* raw_contents0 = contents0.get(); |
| std::unique_ptr<WebContents> contents3 = CreateWebContents(); |
| WebContents* raw_contents3 = contents3.get(); |
| tabstrip()->AppendWebContents(std::move(contents0), false); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AppendWebContents(std::move(contents3), false); |
| observer()->ClearStates(); |
| |
| // Selection and active tab change. |
| tabstrip()->ActivateTabAt( |
| 3, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(3, observer()->GetStateCount()); |
| ASSERT_EQ(observer()->GetStateAt(0).action, |
| MockTabStripModelObserver::DEACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(1).action, |
| MockTabStripModelObserver::ACTIVATE); |
| State s1(raw_contents3, 3, MockTabStripModelObserver::SELECT); |
| s1.src_index = 0; |
| observer()->ExpectStateEquals(2, s1); |
| observer()->ClearStates(); |
| |
| // Adding all tabs to selection, active tab is now at 0. |
| tabstrip()->ExtendSelectionTo(0); |
| ASSERT_EQ(3, observer()->GetStateCount()); |
| ASSERT_EQ(observer()->GetStateAt(0).action, |
| MockTabStripModelObserver::DEACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(1).action, |
| MockTabStripModelObserver::ACTIVATE); |
| State s2(raw_contents0, 0, MockTabStripModelObserver::SELECT); |
| s2.src_index = 3; |
| observer()->ExpectStateEquals(2, s2); |
| observer()->ClearStates(); |
| |
| // Toggle the active tab, should make the next index active. |
| tabstrip()->DeselectTabAt(0); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| EXPECT_EQ(3U, tabstrip()->selection_model().size()); |
| EXPECT_EQ(4, tabstrip()->count()); |
| ASSERT_EQ(3, observer()->GetStateCount()); |
| ASSERT_EQ(observer()->GetStateAt(0).action, |
| MockTabStripModelObserver::DEACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(1).action, |
| MockTabStripModelObserver::ACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(2).action, |
| MockTabStripModelObserver::SELECT); |
| observer()->ClearStates(); |
| |
| // Toggle the first tab back to selected and active. |
| tabstrip()->SelectTabAt(0); |
| EXPECT_EQ(0, tabstrip()->active_index()); |
| EXPECT_EQ(4U, tabstrip()->selection_model().size()); |
| EXPECT_EQ(4, tabstrip()->count()); |
| ASSERT_EQ(3, observer()->GetStateCount()); |
| ASSERT_EQ(observer()->GetStateAt(0).action, |
| MockTabStripModelObserver::DEACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(1).action, |
| MockTabStripModelObserver::ACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(2).action, |
| MockTabStripModelObserver::SELECT); |
| observer()->ClearStates(); |
| |
| // Closing one of the selected tabs, not the active one. |
| tabstrip()->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(3, tabstrip()->count()); |
| ASSERT_EQ(3, observer()->GetStateCount()); |
| ASSERT_EQ(observer()->GetStateAt(0).action, MockTabStripModelObserver::CLOSE); |
| ASSERT_EQ(observer()->GetStateAt(1).action, |
| MockTabStripModelObserver::DETACH); |
| ASSERT_EQ(observer()->GetStateAt(2).action, |
| MockTabStripModelObserver::SELECT); |
| observer()->ClearStates(); |
| |
| // Closing the active tab, while there are others tabs selected. |
| tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_NONE); |
| EXPECT_EQ(2, tabstrip()->count()); |
| ASSERT_EQ(5, observer()->GetStateCount()); |
| ASSERT_EQ(observer()->GetStateAt(0).action, MockTabStripModelObserver::CLOSE); |
| ASSERT_EQ(observer()->GetStateAt(1).action, |
| MockTabStripModelObserver::DETACH); |
| ASSERT_EQ(observer()->GetStateAt(2).action, |
| MockTabStripModelObserver::DEACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(3).action, |
| MockTabStripModelObserver::ACTIVATE); |
| ASSERT_EQ(observer()->GetStateAt(4).action, |
| MockTabStripModelObserver::SELECT); |
| observer()->ClearStates(); |
| |
| // Active tab is at 0, deselecting all but the active tab. |
| tabstrip()->DeselectTabAt(1); |
| ASSERT_EQ(1, observer()->GetStateCount()); |
| ASSERT_EQ(observer()->GetStateAt(0).action, |
| MockTabStripModelObserver::SELECT); |
| observer()->ClearStates(); |
| |
| // Attempting to deselect the only selected and therefore active tab, |
| // it is ignored (no notifications being sent) and tab at 0 remains selected |
| // and active. |
| tabstrip()->DeselectTabAt(0); |
| ASSERT_EQ(0, observer()->GetStateCount()); |
| |
| tabstrip()->RemoveObserver(observer()); |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // Verifies that if we change the selection from a multi selection to a single |
| // selection, but not in a way that changes the selected_index that |
| // TabSelectionChanged is invoked. |
| TEST_F(TabStripModelTest, MultipleToSingle) { |
| typedef MockTabStripModelObserver::State State; |
| |
| std::unique_ptr<WebContents> contents2 = CreateWebContents(); |
| WebContents* raw_contents2 = contents2.get(); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AppendWebContents(std::move(contents2), false); |
| tabstrip()->SelectTabAt(0); |
| tabstrip()->SelectTabAt(1); |
| observer()->ClearStates(); |
| |
| // This changes the selection (0 is no longer selected) but the selected_index |
| // still remains at 1. |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(1, observer()->GetStateCount()); |
| State s(raw_contents2, 1, MockTabStripModelObserver::SELECT); |
| s.src_index = 1; |
| s.change_reason = TabStripModelObserver::CHANGE_REASON_NONE; |
| observer()->ExpectStateEquals(0, s); |
| tabstrip()->RemoveObserver(observer()); |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| namespace { |
| |
| // Test Browser-like class for TabStripModelTest.TabBlockedState. |
| class TabBlockedStateTestBrowser |
| : public TabStripModelObserver, |
| public web_modal::WebContentsModalDialogManagerDelegate { |
| public: |
| explicit TabBlockedStateTestBrowser(TabStripModel* tab_strip_model) |
| : tab_strip_model_(tab_strip_model) { |
| tab_strip_model_->AddObserver(this); |
| } |
| TabBlockedStateTestBrowser(const TabBlockedStateTestBrowser&) = delete; |
| TabBlockedStateTestBrowser& operator=(const TabBlockedStateTestBrowser&) = |
| delete; |
| ~TabBlockedStateTestBrowser() override { |
| tab_strip_model_->RemoveObserver(this); |
| } |
| |
| private: |
| // TabStripModelObserver |
| void OnTabStripModelChanged( |
| TabStripModel* tab_strip_model, |
| const TabStripModelChange& change, |
| const TabStripSelectionChange& selection) override { |
| if (change.type() != TabStripModelChange::kInserted) { |
| return; |
| } |
| |
| for (const auto& contents : change.GetInsert()->contents) { |
| web_modal::WebContentsModalDialogManager* manager = |
| web_modal::WebContentsModalDialogManager::FromWebContents( |
| contents.contents); |
| if (manager) { |
| manager->SetDelegate(this); |
| } |
| } |
| } |
| |
| // WebContentsModalDialogManagerDelegate |
| void SetWebContentsBlocked(content::WebContents* contents, |
| bool blocked) override { |
| int index = tab_strip_model_->GetIndexOfWebContents(contents); |
| |
| // Removal of tabs from the TabStripModel can cause observer callbacks to |
| // invoke this method. The WebContents may no longer exist in the |
| // TabStripModel. |
| if (index == TabStripModel::kNoTab) { |
| return; |
| } |
| |
| tab_strip_model_->SetTabBlocked(index, blocked); |
| } |
| |
| raw_ptr<TabStripModel> tab_strip_model_; |
| }; |
| |
| class DummySingleWebContentsDialogManager |
| : public web_modal::SingleWebContentsDialogManager { |
| public: |
| explicit DummySingleWebContentsDialogManager( |
| gfx::NativeWindow dialog, |
| web_modal::SingleWebContentsDialogManagerDelegate* delegate) |
| : delegate_(delegate), dialog_(dialog) {} |
| DummySingleWebContentsDialogManager( |
| const DummySingleWebContentsDialogManager&) = delete; |
| DummySingleWebContentsDialogManager& operator=( |
| const DummySingleWebContentsDialogManager&) = delete; |
| ~DummySingleWebContentsDialogManager() override = default; |
| |
| void Show() override {} |
| void Hide() override {} |
| void Close() override { delegate_->WillClose(dialog_); } |
| void Focus() override {} |
| void Pulse() override {} |
| void HostChanged(web_modal::WebContentsModalDialogHost* new_host) override {} |
| gfx::NativeWindow dialog() override { return dialog_; } |
| bool IsActive() const override { return true; } |
| |
| private: |
| raw_ptr<web_modal::SingleWebContentsDialogManagerDelegate> delegate_; |
| gfx::NativeWindow dialog_; |
| }; |
| |
| } // namespace |
| |
| // Verifies a newly inserted tab retains its previous blocked state. |
| // http://crbug.com/276334 |
| TEST_F(TabStripModelTest, TabBlockedState) { |
| // Start with a source tab tabstrip()-> |
| TestTabStripModelDelegate dummy_tab_strip_delegate; |
| TabStripModel strip_src(&dummy_tab_strip_delegate, profile()); |
| TabBlockedStateTestBrowser browser_src(&strip_src); |
| |
| // Add a tab. |
| std::unique_ptr<WebContents> contents1 = CreateWebContents(); |
| web_modal::WebContentsModalDialogManager::CreateForWebContents( |
| contents1.get()); |
| strip_src.AppendWebContents(std::move(contents1), /*foreground=*/true); |
| |
| // Add another tab. |
| std::unique_ptr<WebContents> contents2 = CreateWebContents(); |
| WebContents* raw_contents2 = contents2.get(); |
| web_modal::WebContentsModalDialogManager::CreateForWebContents( |
| contents2.get()); |
| strip_src.AppendWebContents(std::move(contents2), false); |
| |
| // Create a destination tab tabstrip()-> |
| TabStripModel strip_dst(&dummy_tab_strip_delegate, profile()); |
| TabBlockedStateTestBrowser browser_dst(&strip_dst); |
| |
| // Setup a SingleWebContentsDialogManager for tab |contents2|. |
| web_modal::WebContentsModalDialogManager* modal_dialog_manager = |
| web_modal::WebContentsModalDialogManager::FromWebContents(raw_contents2); |
| |
| // Show a dialog that blocks tab |contents2|. |
| // DummySingleWebContentsDialogManager doesn't care about the |
| // dialog window value, so any dummy value works. |
| DummySingleWebContentsDialogManager* native_manager = |
| new DummySingleWebContentsDialogManager(gfx::NativeWindow(), |
| modal_dialog_manager); |
| modal_dialog_manager->ShowDialogWithManager( |
| gfx::NativeWindow(), |
| std::unique_ptr<web_modal::SingleWebContentsDialogManager>( |
| native_manager)); |
| EXPECT_TRUE(strip_src.IsTabBlocked(1)); |
| |
| // Detach the tab. |
| std::unique_ptr<tabs::TabModel> moved_tab = |
| strip_src.DetachTabAtForInsertion(1); |
| EXPECT_EQ(raw_contents2, moved_tab->GetContents()); |
| |
| // Attach the tab to the destination tab tabstrip()-> |
| strip_dst.AppendTab(std::move(moved_tab), true); |
| EXPECT_TRUE(strip_dst.IsTabBlocked(0)); |
| |
| strip_dst.CloseAllTabs(); |
| strip_src.CloseAllTabs(); |
| } |
| |
| // Verifies ordering of tabs opened via a link from a pinned tab with a |
| // subsequent pinned tab. |
| TEST_F(TabStripModelTest, LinkClicksWithPinnedTabOrdering) { |
| |
| // Open two pages, pinned. |
| tabstrip()->AddWebContents(CreateWebContents(), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_ACTIVE | AddTabTypes::ADD_PINNED); |
| tabstrip()->AddWebContents(CreateWebContents(), -1, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| AddTabTypes::ADD_ACTIVE | AddTabTypes::ADD_PINNED); |
| |
| // Activate the first tab (a). |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Open two more tabs as link clicks. The first tab, c, should appear after |
| // the pinned tabs followed by the second tab (d). |
| std::unique_ptr<WebContents> page_c_contents = CreateWebContents(); |
| WebContents* raw_page_c_contents = page_c_contents.get(); |
| std::unique_ptr<WebContents> page_d_contents = CreateWebContents(); |
| WebContents* raw_page_d_contents = page_d_contents.get(); |
| tabstrip()->AddWebContents(std::move(page_c_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| tabstrip()->AddWebContents(std::move(page_d_contents), -1, |
| ui::PAGE_TRANSITION_LINK, AddTabTypes::ADD_NONE); |
| |
| EXPECT_EQ(2, tabstrip()->GetIndexOfWebContents(raw_page_c_contents)); |
| EXPECT_EQ(3, tabstrip()->GetIndexOfWebContents(raw_page_d_contents)); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // This test covers a bug in TabStripModel::MoveWebContentsAt(). Specifically |
| // if |select_after_move| was true it checked if the index |
| // select_after_move (as an int) was selected rather than |to_position|. |
| TEST_F(TabStripModelTest, MoveWebContentsAt) { |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(1, tabstrip()->active_index()); |
| |
| tabstrip()->MoveWebContentsAt(2, 3, true); |
| EXPECT_EQ(3, tabstrip()->active_index()); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, NewTabsUngrouped) { |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| |
| EXPECT_FALSE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroup) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| |
| tabstrip()->AddToNewGroup({0}); |
| |
| EXPECT_TRUE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, FindGroupIdFor) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| { |
| auto result = tabstrip()->FindGroupIdFor(tabs::TabCollection::Handle(888)); |
| ASSERT_FALSE(result.has_value()); |
| } |
| |
| { |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| auto* tab = tabstrip()->GetTabAtIndex(0); |
| |
| ASSERT_TRUE(tab != nullptr); |
| |
| auto group_id = tabstrip()->AddToNewGroup({0}); |
| |
| auto* collection = tab->GetParentCollection(); |
| ASSERT_TRUE(collection != nullptr); |
| |
| auto result = tabstrip()->FindGroupIdFor(collection->GetHandle()); |
| ASSERT_TRUE(result.has_value()); |
| ASSERT_EQ(group_id, result.value()); |
| } |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroupUpdatesObservers) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| |
| observer()->ClearStates(); |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(1, observer()->group_update(group).contents_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, ReplacingTabGroupUpdatesObservers) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| |
| observer()->ClearStates(); |
| tab_groups::TabGroupId first_group = tabstrip()->AddToNewGroup({0, 1}); |
| tab_groups::TabGroupId second_group = tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(2u, observer()->group_updates().size()); |
| EXPECT_EQ(3, observer()->group_update(first_group).contents_update_count); |
| EXPECT_EQ(1, observer()->group_update(second_group).contents_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroupMiddleOfExistingGroup) { |
| ASSERT_TRUE(tabstrip()->SupportsTabGroups()); |
| |
| PrepareTabs(tabstrip(), 4); |
| tabstrip()->AddToNewGroup({0, 1, 2, 3}); |
| |
| tabstrip()->AddToNewGroup({1, 2}); |
| EXPECT_EQ("0g0 3g0 1g1 2g1", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroupMiddleOfExistingGroupTwoGroups) { |
| PrepareTabs(tabstrip(), 4); |
| tabstrip()->AddToNewGroup({0, 1, 2}); |
| tabstrip()->AddToNewGroup({3}); |
| |
| tabstrip()->AddToNewGroup({1}); |
| EXPECT_EQ("0g0 2g0 1g1 3g2", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroupReorders) { |
| PrepareTabs(tabstrip(), 3); |
| |
| tabstrip()->AddToNewGroup({0, 2}); |
| |
| EXPECT_EQ("0g0 2g0 1", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroupUnpins) { |
| PrepareTabs(tabstrip(), 2); |
| |
| tabstrip()->SetTabPinned(0, true); |
| tabstrip()->AddToNewGroup({0, 1}); |
| |
| EXPECT_EQ("0g0 1g0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroupUnpinsAndReorders) { |
| PrepareTabs(tabstrip(), 3); |
| |
| tabstrip()->SetTabPinned(0, true); |
| tabstrip()->SetTabPinned(1, true); |
| tabstrip()->AddToNewGroup({0}); |
| |
| EXPECT_EQ("1p 0g0 2", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToNewGroupMovesPinnedAndUnpinnedTabs) { |
| PrepareTabs(tabstrip(), 4); |
| |
| tabstrip()->SetTabPinned(0, true); |
| tabstrip()->SetTabPinned(1, true); |
| tabstrip()->SetTabPinned(2, true); |
| tabstrip()->AddToNewGroup({0, 1}); |
| EXPECT_EQ("2p 0g0 1g0 3", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->AddToNewGroup({0, 2}); |
| EXPECT_EQ("2g0 1g0 0g1 3", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabAndSplitsToNewGroup) { |
| PrepareTabs(tabstrip(), 8); |
| |
| tabstrip()->SetTabPinned(0, true); |
| tabstrip()->SetTabPinned(1, true); |
| tabstrip()->SetTabPinned(2, true); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->SetTabPinned(3, true); |
| tabstrip()->ActivateTabAt(5); |
| tabstrip()->AddToNewSplit({6}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0p 1ps 2ps 3p 4 5s 6s 7", |
| GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->AddToNewGroup({0, 1, 2, 5, 6, 7}); |
| EXPECT_EQ("3p 0g0 1g0s 2g0s 5g0s 6g0s 7g0 4", |
| GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->AddToNewGroup({0, 2, 3, 6}); |
| EXPECT_EQ("3g0 1g0s 2g0s 7g0 0g1 5g1s 6g1s 4", |
| GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToExistingGroupIdempotent) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| |
| tabstrip()->AddToNewGroup({0}); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(0); |
| tabstrip()->AddToExistingGroup({0}, group.value()); |
| |
| observer()->ClearStates(); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), group); |
| EXPECT_EQ(0, observer()->GetStateCount()); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToExistingGroup) { |
| PrepareTabs(tabstrip(), 2); |
| |
| tabstrip()->AddToNewGroup({0}); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(0); |
| |
| tabstrip()->AddToExistingGroup({1}, group.value()); |
| EXPECT_EQ("0g0 1g0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToExistingGroupUpdatesObservers) { |
| PrepareTabs(tabstrip(), 2); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(1, observer()->group_update(group).contents_update_count); |
| observer()->ClearStates(); |
| ASSERT_EQ(0u, observer()->group_updates().size()); |
| |
| tabstrip()->AddToExistingGroup({1}, group); |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(1, observer()->group_update(group).contents_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToLeftOfExistingGroupReorders) { |
| PrepareTabs(tabstrip(), 3); |
| |
| tabstrip()->AddToNewGroup({2}); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(2); |
| |
| tabstrip()->AddToExistingGroup({0}, group.value()); |
| EXPECT_EQ("1 0g0 2g0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToRighOfExistingGroupReorders) { |
| PrepareTabs(tabstrip(), 3); |
| |
| tabstrip()->AddToNewGroup({0}); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(0); |
| |
| tabstrip()->AddToExistingGroup({2}, group.value()); |
| EXPECT_EQ("0g0 2g0 1", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToExistingGroupReorders) { |
| PrepareTabs(tabstrip(), 4); |
| |
| tabstrip()->AddToNewGroup({1}); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(1); |
| |
| tabstrip()->AddToExistingGroup({0, 3}, group.value()); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), group); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), group); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(2), group); |
| EXPECT_FALSE(tabstrip()->GetTabGroupForTab(3).has_value()); |
| EXPECT_EQ("0 1 3 2", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabToExistingGroupUnpins) { |
| PrepareTabs(tabstrip(), 2); |
| |
| tabstrip()->SetTabPinned(0, true); |
| tabstrip()->AddToNewGroup({1}); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(1); |
| |
| tabstrip()->AddToExistingGroup({0}, group.value()); |
| EXPECT_FALSE(tabstrip()->IsTabPinned(0)); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), group); |
| EXPECT_EQ("0 1", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddTabAndSplitsToExistingGroup) { |
| PrepareTabs(tabstrip(), 8); |
| |
| tabstrip()->SetTabPinned(0, true); |
| tabstrip()->SetTabPinned(1, true); |
| tabstrip()->SetTabPinned(2, true); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->SetTabPinned(3, true); |
| tabstrip()->ActivateTabAt(5); |
| tabstrip()->AddToNewSplit({6}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->AddToNewGroup({4}); |
| ASSERT_EQ("0p 1ps 2ps 3p 4g0 5s 6s 7", |
| GetTabStripStateString(tabstrip(), true)); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(4); |
| |
| tabstrip()->AddToExistingGroup({0, 1, 2, 5, 6, 7}, group.value()); |
| EXPECT_EQ("3p 0g0 1g0s 2g0s 4g0 5g0s 6g0s 7g0", |
| GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, PinTabInGroupUngroups) { |
| PrepareTabs(tabstrip(), 2); |
| |
| tabstrip()->AddToNewGroup({0, 1}); |
| EXPECT_EQ(0, tabstrip()->SetTabPinned(1, true)); |
| |
| EXPECT_FALSE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| EXPECT_TRUE(tabstrip()->GetTabGroupForTab(1).has_value()); |
| EXPECT_EQ("1p 0", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabFromGroupNoopForUngroupedTab) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| |
| observer()->ClearStates(); |
| tabstrip()->RemoveFromGroup({0}); |
| EXPECT_EQ(0, observer()->GetStateCount()); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabFromGroup) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| |
| tabstrip()->RemoveFromGroup({0}); |
| |
| EXPECT_FALSE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabFromGroupUpdatesObservers) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| observer()->ClearStates(); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(1, observer()->group_update(group).contents_update_count); |
| |
| tabstrip()->RemoveFromGroup({0}); |
| EXPECT_EQ(0u, observer()->group_updates().size()); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabFromGroupMaintainsOrder) { |
| PrepareTabs(tabstrip(), 2); |
| tabstrip()->AddToNewGroup({0, 1}); |
| |
| tabstrip()->RemoveFromGroup({0}); |
| EXPECT_EQ("0 1g0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabFromGroupDoesntReorderIfNoGroup) { |
| PrepareTabs(tabstrip(), 3); |
| tabstrip()->AddToNewGroup({0}); |
| |
| tabstrip()->RemoveFromGroup({0, 1}); |
| EXPECT_EQ("0 1 2", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, |
| RemoveTabFromGroupMaintainsRelativeOrderOfSelectedTabs) { |
| PrepareTabs(tabstrip(), 4); |
| tabstrip()->AddToNewGroup({0, 1, 2, 3}); |
| |
| tabstrip()->RemoveFromGroup({0, 2}); |
| EXPECT_EQ("0 1g0 3g0 2", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabFromGroupMixtureOfGroups) { |
| PrepareTabs(tabstrip(), 5); |
| tabstrip()->AddToNewGroup({0, 1}); |
| tabstrip()->AddToNewGroup({2, 3}); |
| |
| tabstrip()->RemoveFromGroup({0, 3, 4}); |
| EXPECT_EQ("0 1g0 2g1 3 4", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabFromGroupDeletesGroup) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 1U); |
| |
| tabstrip()->RemoveFromGroup({0}); |
| |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 0U); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveTabsAndSplitsFromGroup) { |
| PrepareTabs(tabstrip(), 5); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->AddToNewGroup({0, 1, 2, 3, 4}); |
| ASSERT_EQ("0g0 1g0s 2g0s 3g0 4g0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->RemoveFromGroup({1, 2, 3}); |
| EXPECT_EQ("1s 2s 0g0 4g0 3", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| |
| PrepareTabs(tabstrip(), 5); |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->AddToNewSplit({3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->AddToNewGroup({0, 1, 2, 3, 4}); |
| ASSERT_EQ("0g0 1g0 2g0s 3g0s 4g0", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->RemoveFromGroup({1, 2, 3}); |
| EXPECT_EQ("1 0g0 4g0 2s 3s", GetTabStripStateString(tabstrip(), true)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddToNewGroupDeletesGroup) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 1U); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(0); |
| |
| tabstrip()->AddToNewGroup({0}); |
| |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 1U); |
| EXPECT_NE(tabstrip()->GetTabGroupForTab(0), group); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveGroupToTest) { |
| PrepareTabs(tabstrip(), 5); |
| |
| const tab_groups::TabGroupId group1 = tabstrip()->AddToNewGroup({0, 1, 2}); |
| tabstrip()->MoveGroupTo(group1, 2); |
| |
| EXPECT_EQ("3 4 0 1 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group1, tabstrip()->GetTabGroupForTab(4)); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AddToExistingGroupDeletesGroup) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AddToNewGroup({0}); |
| tabstrip()->AddToNewGroup({1}); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 2U); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(1); |
| |
| tabstrip()->AddToExistingGroup({1}, tabstrip()->GetTabGroupForTab(0).value()); |
| |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 1U); |
| EXPECT_NE(tabstrip()->group_model()->ListTabGroups()[0], group); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, CloseTabDeletesGroup) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 1U); |
| |
| tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_USER_GESTURE); |
| |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 0U); |
| } |
| |
| TEST_F(TabStripModelTest, CloseTabNotifiesObserversOfGroupChange) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| observer()->ClearStates(); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0}); |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(1, observer()->group_update(group).contents_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_USER_GESTURE); |
| EXPECT_EQ(0u, observer()->group_updates().size()); |
| } |
| |
| TEST_F(TabStripModelTest, InsertWebContentsAtWithGroupNotifiesObservers) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| observer()->ClearStates(); |
| |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0, 1}); |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(2, observer()->group_update(group).contents_update_count); |
| |
| tabstrip()->InsertWebContentsAt(1, CreateWebContents(), AddTabTypes::ADD_NONE, |
| group); |
| |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(3, observer()->group_update(group).contents_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // When inserting a WebContents, if a group is not specified, the new tab |
| // should be left ungrouped. |
| TEST_F(TabStripModelTest, InsertWebContentsAtDoesNotGroupByDefault) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AppendWebContents(CreateWebContents(), false); |
| tabstrip()->AddToNewGroup({0, 1}); |
| |
| tabstrip()->InsertWebContentsAt(2, CreateWebContents(), |
| AddTabTypes::ADD_NONE); |
| |
| // The newly added tab should not be in the group. |
| EXPECT_TRUE(tabstrip()->GetTabGroupForTab(0).has_value()); |
| EXPECT_TRUE(tabstrip()->GetTabGroupForTab(1).has_value()); |
| EXPECT_FALSE(tabstrip()->GetTabGroupForTab(2).has_value()); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // When inserting a WebContents, if a group is specified, the new tab should be |
| // added to that group. |
| TEST_F(TabStripModelTest, InsertWebContentsAtWithGroupGroups) { |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(0), true); |
| tabstrip()->AppendWebContents(CreateWebContentsWithID(1), false); |
| tabstrip()->AddToNewGroup({0, 1}); |
| std::optional<tab_groups::TabGroupId> group = |
| tabstrip()->GetTabGroupForTab(0); |
| |
| tabstrip()->InsertWebContentsAt(1, CreateWebContentsWithID(2), |
| AddTabTypes::ADD_NONE, group); |
| |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(0), group); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(1), group); |
| EXPECT_EQ(tabstrip()->GetTabGroupForTab(2), group); |
| EXPECT_EQ("0 2 1", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, NewTabWithGroup) { |
| PrepareTabs(tabstrip(), 3); |
| auto group = tabstrip()->AddToNewGroup({1}); |
| |
| tabstrip()->AddWebContents(CreateWebContentsWithID(3), 2, |
| ui::PAGE_TRANSITION_TYPED, AddTabTypes::ADD_NONE, |
| group); |
| EXPECT_EQ("0 1 3 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, NewTabWithGroupDeletedCorrectly) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| tabstrip()->InsertWebContentsAt(1, CreateWebContents(), AddTabTypes::ADD_NONE, |
| tabstrip()->GetTabGroupForTab(0)); |
| |
| tabstrip()->RemoveFromGroup({1}); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 1U); |
| tabstrip()->RemoveFromGroup({0}); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 0U); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, NewTabWithoutIndexInsertsAtEndOfGroup) { |
| PrepareTabs(tabstrip(), 3); |
| auto group = tabstrip()->AddToNewGroup({0, 1}); |
| |
| tabstrip()->AddWebContents(CreateWebContentsWithID(3), -1, |
| ui::PAGE_TRANSITION_TYPED, AddTabTypes::ADD_NONE, |
| group); |
| EXPECT_EQ("0 1 3 2", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, DiscontinuousNewTabIndexTooHigh) { |
| PrepareTabs(tabstrip(), 3); |
| auto group = tabstrip()->AddToNewGroup({0, 1}); |
| |
| tabstrip()->AddWebContents(CreateWebContentsWithID(3), 3, |
| ui::PAGE_TRANSITION_TYPED, AddTabTypes::ADD_NONE, |
| group); |
| EXPECT_EQ("0 1 3 2", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, DiscontinuousNewTabIndexTooLow) { |
| PrepareTabs(tabstrip(), 3); |
| auto group = tabstrip()->AddToNewGroup({1, 2}); |
| |
| tabstrip()->AddWebContents(CreateWebContentsWithID(3), 0, |
| ui::PAGE_TRANSITION_TYPED, AddTabTypes::ADD_NONE, |
| group); |
| EXPECT_EQ("0 3 1 2", GetTabStripStateString(tabstrip())); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, CreateGroupSetsVisualData) { |
| tab_groups::ColorLabelMap all_colors = tab_groups::GetTabGroupColorLabelMap(); |
| PrepareTabs(tabstrip(), all_colors.size() + 1); |
| |
| // Expect groups to cycle through the available color set. |
| int index = 0; |
| for (const auto& color_pair : all_colors) { |
| const tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({index}); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group)->visual_data()->color(), |
| color_pair.first); |
| ++index; |
| } |
| |
| // Expect the last group to cycle back to the first color. |
| const tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({index}); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group)->visual_data()->color(), |
| all_colors.begin()->first); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, SetVisualDataForGroup) { |
| PrepareTabs(tabstrip(), 1); |
| const tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0}); |
| |
| const tab_groups::TabGroupVisualData new_data( |
| u"Foo", tab_groups::TabGroupColorId::kCyan); |
| tabstrip()->ChangeTabGroupVisuals(group, new_data); |
| const tab_groups::TabGroupVisualData* data = |
| tabstrip()->group_model()->GetTabGroup(group)->visual_data(); |
| EXPECT_EQ(data->title(), new_data.title()); |
| EXPECT_EQ(data->color(), new_data.color()); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, VisualDataChangeNotifiesObservers) { |
| PrepareTabs(tabstrip(), 1); |
| const tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0}); |
| |
| // Check that we are notified about the placeholder |
| // tab_groups::TabGroupVisualData. |
| ASSERT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(1, observer()->group_update(group).contents_update_count); |
| |
| const tab_groups::TabGroupVisualData new_data( |
| u"Foo", tab_groups::TabGroupColorId::kBlue); |
| tabstrip()->ChangeTabGroupVisuals(group, new_data); |
| |
| // Now check that we are notified when we change it, once at creation |
| // and once from the explicit update. |
| ASSERT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(2, observer()->group_update(group).visuals_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingTabToStartOfGroupDoesNotChangeGroup) { |
| PrepareTabs(tabstrip(), 5); |
| const auto group = tabstrip()->AddToNewGroup({1, 2, 3}); |
| |
| tabstrip()->MoveWebContentsAt(2, 1, false); |
| EXPECT_EQ("0 2 1 3 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(3)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingTabToMiddleOfGroupDoesNotChangeGroup) { |
| PrepareTabs(tabstrip(), 5); |
| const auto group = tabstrip()->AddToNewGroup({1, 2, 3}); |
| |
| tabstrip()->MoveWebContentsAt(1, 2, false); |
| EXPECT_EQ("0 2 1 3 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(3)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingTabToEndOfGroupDoesNotChangeGroup) { |
| PrepareTabs(tabstrip(), 5); |
| const auto group = tabstrip()->AddToNewGroup({1, 2, 3}); |
| |
| tabstrip()->MoveWebContentsAt(2, 3, false); |
| EXPECT_EQ("0 1 3 2 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(3)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingTabOutsideOfGroupToStartOfTabstripClearsGroup) { |
| PrepareTabs(tabstrip(), 5); |
| const auto group = tabstrip()->AddToNewGroup({1, 2, 3}); |
| |
| tabstrip()->MoveWebContentsAt(1, 0, false); |
| EXPECT_EQ("1 0 2 3 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(0)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingTabOutsideOfGroupToEndOfTabstripClearsGroup) { |
| PrepareTabs(tabstrip(), 5); |
| const auto group = tabstrip()->AddToNewGroup({1, 2, 3}); |
| |
| tabstrip()->MoveWebContentsAt(3, 4, false); |
| EXPECT_EQ("0 1 2 4 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(3)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(4)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingTabBetweenUngroupedTabsClearsGroup) { |
| PrepareTabs(tabstrip(), 5); |
| tabstrip()->AddToNewGroup({0, 1, 2}); |
| |
| tabstrip()->MoveWebContentsAt(1, 3, false); |
| EXPECT_EQ("0 2 3 1 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(3)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(4)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingUngroupedTabBetweenGroupsDoesNotAssignGroup) { |
| PrepareTabs(tabstrip(), 5); |
| const auto group1 = tabstrip()->AddToNewGroup({1, 2}); |
| const auto group2 = tabstrip()->AddToNewGroup({3, 4}); |
| |
| tabstrip()->MoveWebContentsAt(0, 2, false); |
| EXPECT_EQ("1 2 0 3 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group1, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(3)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, |
| MovingUngroupedTabBetweenGroupAndUngroupedDoesNotAssignGroup) { |
| PrepareTabs(tabstrip(), 4); |
| const auto group = tabstrip()->AddToNewGroup({1, 2}); |
| |
| tabstrip()->MoveWebContentsAt(0, 2, false); |
| EXPECT_EQ("1 2 0 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(3)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, |
| MovingUngroupedTabBetweenUngroupedAndGroupDoesNotAssignGroup) { |
| PrepareTabs(tabstrip(), 4); |
| const auto group = tabstrip()->AddToNewGroup({2, 3}); |
| |
| tabstrip()->MoveWebContentsAt(0, 1, false); |
| EXPECT_EQ("1 0 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(0)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, |
| MovingGroupMemberBetweenTwoDifferentGroupsClearsGroup) { |
| PrepareTabs(tabstrip(), 6); |
| const auto group1 = tabstrip()->AddToNewGroup({0, 1}); |
| const auto group2 = tabstrip()->AddToNewGroup({2, 3}); |
| const auto group3 = tabstrip()->AddToNewGroup({4, 5}); |
| |
| tabstrip()->MoveWebContentsAt(0, 3, false); |
| EXPECT_EQ("1 2 3 0 4 5", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group1, tabstrip()->GetTabGroupForTab(0)); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(3)); |
| EXPECT_EQ(group3, tabstrip()->GetTabGroupForTab(4)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, |
| MovingSingleTabGroupBetweenTwoGroupsDoesNotClearGroup) { |
| PrepareTabs(tabstrip(), 5); |
| const auto group1 = tabstrip()->AddToNewGroup({0}); |
| const auto group2 = tabstrip()->AddToNewGroup({1, 2}); |
| const auto group3 = tabstrip()->AddToNewGroup({3, 4}); |
| |
| tabstrip()->MoveWebContentsAt(0, 2, false); |
| EXPECT_EQ("1 2 0 3 4", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(0)); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group1, tabstrip()->GetTabGroupForTab(2)); |
| EXPECT_EQ(group3, tabstrip()->GetTabGroupForTab(3)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingUngroupedTabIntoGroupSetsGroup) { |
| PrepareTabs(tabstrip(), 3); |
| const auto group = tabstrip()->AddToNewGroup({1, 2}); |
| |
| tabstrip()->MoveWebContentsAt(0, 1, false); |
| EXPECT_EQ("1 0 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(0)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group, tabstrip()->GetTabGroupForTab(2)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MovingGroupedTabIntoGroupChangesGroup) { |
| PrepareTabs(tabstrip(), 3); |
| tabstrip()->AddToNewGroup({0}); |
| const auto group2 = tabstrip()->AddToNewGroup({1, 2}); |
| |
| tabstrip()->MoveWebContentsAt(0, 1, false); |
| EXPECT_EQ("1 0 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(0)); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(1)); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(2)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveWebContentsAtCorrectlyRemovesGroupEntries) { |
| PrepareTabs(tabstrip(), 3); |
| tabstrip()->AddToNewGroup({0}); |
| const auto group2 = tabstrip()->AddToNewGroup({1, 2}); |
| |
| tabstrip()->MoveWebContentsAt(0, 1, false); |
| EXPECT_EQ("1 0 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(1)); |
| const std::vector<tab_groups::TabGroupId> expected_groups{group2}; |
| EXPECT_EQ(expected_groups, tabstrip()->group_model()->ListTabGroups()); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveWebContentsAtCorrectlySendsGroupChangedEvent) { |
| PrepareTabs(tabstrip(), 3); |
| |
| const tab_groups::TabGroupId group1 = tabstrip()->AddToNewGroup({0}); |
| const tab_groups::TabGroupId group2 = tabstrip()->AddToNewGroup({1, 2}); |
| |
| EXPECT_EQ(2u, observer()->group_updates().size()); |
| EXPECT_EQ(1, observer()->group_update(group1).contents_update_count); |
| EXPECT_EQ(2, observer()->group_update(group2).contents_update_count); |
| |
| tabstrip()->MoveWebContentsAt(0, 1, false); |
| EXPECT_EQ("1 0 2", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(group2, tabstrip()->GetTabGroupForTab(1)); |
| |
| // group1 should be deleted. group2 should have an update. |
| EXPECT_EQ(1u, observer()->group_updates().size()); |
| EXPECT_EQ(3, observer()->group_update(group2).contents_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, MoveWebContentsAtCorrectlySendsGroupClearedEvent) { |
| PrepareTabs(tabstrip(), 3); |
| |
| const tab_groups::TabGroupId group1 = tabstrip()->AddToNewGroup({0, 1}); |
| const tab_groups::TabGroupId group2 = tabstrip()->AddToNewGroup({2}); |
| EXPECT_EQ(2u, observer()->group_updates().size()); |
| EXPECT_EQ(2, observer()->group_update(group1).contents_update_count); |
| EXPECT_EQ(1, observer()->group_update(group2).contents_update_count); |
| |
| tabstrip()->MoveWebContentsAt(0, 2, false); |
| EXPECT_EQ("1 2 0", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetTabGroupForTab(2)); |
| |
| // The tab should be removed from group1 but not added to group2. |
| EXPECT_EQ(2u, observer()->group_updates().size()); |
| EXPECT_EQ(3, observer()->group_update(group1).contents_update_count); |
| EXPECT_EQ(1, observer()->group_update(group2).contents_update_count); |
| |
| observer()->ClearStates(); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // Ensure that the opener for a tab never refers to a dangling WebContents. |
| // Regression test for crbug.com/1092308. |
| TEST_F(TabStripModelTest, DanglingOpener) { |
| PrepareTabs(tabstrip(), 2); |
| |
| WebContents* contents_1 = tabstrip()->GetWebContentsAt(0); |
| WebContents* contents_2 = tabstrip()->GetWebContentsAt(1); |
| ASSERT_TRUE(contents_1); |
| ASSERT_TRUE(contents_2); |
| |
| // Set the openers for the two tabs to each other. |
| tabstrip()->SetOpenerOfWebContentsAt(0, contents_2); |
| tabstrip()->SetOpenerOfWebContentsAt(1, contents_1); |
| EXPECT_EQ("0 1", GetTabStripStateString(tabstrip())); |
| |
| // Move the first tab to the end of the tab tabstrip()-> |
| EXPECT_EQ(1, |
| tabstrip()->MoveWebContentsAt(0, 1, false /* select_after_move */)); |
| EXPECT_EQ("1 0", GetTabStripStateString(tabstrip())); |
| |
| // Replace the WebContents at index 0 with a new WebContents. |
| std::unique_ptr<WebContents> replaced_contents = |
| tabstrip()->DiscardWebContentsAt(0, CreateWebContentsWithID(5)); |
| EXPECT_EQ(contents_2, replaced_contents.get()); |
| replaced_contents.reset(); |
| EXPECT_EQ("5 0", GetTabStripStateString(tabstrip())); |
| ASSERT_TRUE(contents_2); |
| |
| // Ensure the opener for the tab at index 0 isn't dangling. It should be null |
| // instead. |
| tabs::TabInterface* opener = tabstrip()->GetOpenerOfTabAt(0); |
| EXPECT_FALSE(opener); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| class TabToWindowTestTabStripModelDelegate : public TestTabStripModelDelegate { |
| public: |
| bool CanMoveTabsToWindow(const std::vector<int>& indices) override { |
| for (int index : indices) { |
| can_move_calls_.push_back(index); |
| } |
| return true; |
| } |
| |
| void MoveTabsToNewWindow(const std::vector<int>& indices) override { |
| for (int index : indices) { |
| move_calls_.push_back(index); |
| } |
| } |
| |
| void MoveGroupToNewWindow(const tab_groups::TabGroupId& group) override {} |
| |
| std::vector<int> can_move_calls() { return can_move_calls_; } |
| std::vector<int> move_calls() { return move_calls_; } |
| |
| private: |
| std::vector<int> can_move_calls_; |
| std::vector<int> move_calls_; |
| }; |
| |
| // Sanity check to ensure that the "Move Tabs to Window" command talks to |
| // the delegate correctly. |
| TEST_F(TabStripModelTest, MoveTabsToNewWindow) { |
| TabToWindowTestTabStripModelDelegate delegate; |
| TabStripModel tabstrip(&delegate, profile()); |
| PrepareTabs(&tabstrip, 3); |
| |
| EXPECT_EQ(delegate.can_move_calls().size(), 0u); |
| EXPECT_EQ(delegate.move_calls().size(), 0u); |
| |
| EXPECT_TRUE(tabstrip.IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandMoveTabsToNewWindow)); |
| |
| // ASSERT and not EXPECT since we're accessing back() later. |
| ASSERT_EQ(delegate.can_move_calls().size(), 1u); |
| EXPECT_EQ(delegate.move_calls().size(), 0u); |
| EXPECT_EQ(delegate.can_move_calls().back(), 2); |
| |
| tabstrip.ExecuteContextMenuCommand(0, |
| TabStripModel::CommandMoveTabsToNewWindow); |
| // Whether ExecuteCommand checks if the command is valid or not is an |
| // implementation detail, so let's not be brittle. |
| EXPECT_GT(delegate.can_move_calls().size(), 0u); |
| ASSERT_EQ(delegate.move_calls().size(), 1u); |
| EXPECT_EQ(delegate.move_calls().back(), 0); |
| |
| tabstrip.CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, SurroundingGroupAtIndex) { |
| PrepareTabs(tabstrip(), 4); |
| |
| auto group1 = tabstrip()->AddToNewGroup({1, 2}); |
| tabstrip()->AddToNewGroup({3}); |
| |
| EXPECT_EQ(std::nullopt, tabstrip()->GetSurroundingTabGroup(0)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetSurroundingTabGroup(1)); |
| EXPECT_EQ(group1, tabstrip()->GetSurroundingTabGroup(2)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetSurroundingTabGroup(3)); |
| EXPECT_EQ(std::nullopt, tabstrip()->GetSurroundingTabGroup(4)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, ActivateRecordsStartTime) { |
| PrepareTabs(tabstrip(), 2); |
| |
| // PrepareTabs should leave the last tab active. |
| ASSERT_EQ(tabstrip()->GetActiveWebContents(), |
| tabstrip()->GetWebContentsAt(1)); |
| ASSERT_FALSE(HasTabSwitchStartTimeAtIndex(0)); |
| ASSERT_FALSE(HasTabSwitchStartTimeAtIndex(1)); |
| |
| // ActivateTabAt should only update the start time if the active tab changes. |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(0)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(1)); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_TRUE(HasTabSwitchStartTimeAtIndex(0)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(1)); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_TRUE(HasTabSwitchStartTimeAtIndex(0)); |
| EXPECT_TRUE(HasTabSwitchStartTimeAtIndex(1)); |
| } |
| |
| TEST_F(TabStripModelTest, ActivateRecordsStartTime_UnSplitToSplit) { |
| // Create 3 tabs with a split containing tabs 1 and 2. |
| PrepareTabs(tabstrip(), 3); |
| ASSERT_EQ(tabstrip()->GetActiveWebContents(), |
| tabstrip()->GetWebContentsAt(2)); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // ActivateTabAt should update the start time when the tab outside the split |
| // becomes active. |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(0)); |
| tabstrip()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_TRUE(HasTabSwitchStartTimeAtIndex(0)); |
| } |
| |
| TEST_F(TabStripModelTest, ActivateRecordsStartTime_SplitToUnSplit) { |
| // Create 3 tabs with a split containing tabs 1 and 2. |
| PrepareTabs(tabstrip(), 3); |
| ASSERT_EQ(tabstrip()->GetActiveWebContents(), |
| tabstrip()->GetWebContentsAt(2)); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(0); |
| |
| // ActivateTabAt should update the start time when a tab in the split becomes |
| // active. Only the tab that was activated should get a start time. |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(1)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(2)); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_TRUE(HasTabSwitchStartTimeAtIndex(1)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(2)); |
| } |
| |
| TEST_F(TabStripModelTest, ActivateRecordsStartTime_SplitToSameSplit) { |
| // Create 3 tabs with a split containing tabs 1 and 2. |
| PrepareTabs(tabstrip(), 3); |
| ASSERT_EQ(tabstrip()->GetActiveWebContents(), |
| tabstrip()->GetWebContentsAt(2)); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // ActivateTabAt should not update the start time when the other tab in the |
| // split becomes active. |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(1)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(2)); |
| tabstrip()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(1)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(2)); |
| } |
| |
| TEST_F(TabStripModelTest, ActivateRecordsStartTime_SplitToOtherSplit) { |
| // Create 4 tabs, with a split containing tabs 0 and 1, and a split containing |
| // tabs 2 and 3. |
| PrepareTabs(tabstrip(), 4); |
| ASSERT_EQ(tabstrip()->GetActiveWebContents(), |
| tabstrip()->GetWebContentsAt(3)); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // ActivateTabAt should update the start time when a tab in the other split |
| // becomes active. Only the tab that was activated should get a start time. |
| ASSERT_FALSE(HasTabSwitchStartTimeAtIndex(2)); |
| ASSERT_FALSE(HasTabSwitchStartTimeAtIndex(3)); |
| tabstrip()->ActivateTabAt( |
| 2, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_TRUE(HasTabSwitchStartTimeAtIndex(2)); |
| EXPECT_FALSE(HasTabSwitchStartTimeAtIndex(3)); |
| } |
| |
| TEST_F(TabStripModelTest, ToggleSiteMuted) { |
| |
| GURL url("https://example.com/"); |
| HostContentSettingsMap* settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| |
| std::unique_ptr<WebContents> new_tab_contents = CreateWebContents(); |
| content::WebContentsTester::For(new_tab_contents.get()) |
| ->SetLastCommittedURL(url); |
| |
| tabstrip()->AddWebContents(std::move(new_tab_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Validate if the mute site menu item shows up and the site is unmuted |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandToggleSiteMuted)); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| |
| // Validate if toggling the state successfully mutes the site |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_TRUE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_TRUE(IsSiteInContentSettingExceptionList(settings, url, |
| ContentSettingsType::SOUND)); |
| |
| // Toggling the state again to successfully unmute the site |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_FALSE(IsSiteInContentSettingExceptionList(settings, url, |
| ContentSettingsType::SOUND)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, ToggleSiteMutedWithLessSpecificRule) { |
| |
| GURL url("https://example.com/"); |
| HostContentSettingsMap* settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| |
| std::unique_ptr<WebContents> new_tab_contents = CreateWebContents(); |
| content::WebContentsTester::For(new_tab_contents.get()) |
| ->SetLastCommittedURL(url); |
| |
| tabstrip()->AddWebContents(std::move(new_tab_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Validate if the mute site menu item shows up and the site is unmuted |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandToggleSiteMuted)); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| |
| // Add a wildcard to mute all HTTPS sites as a custom behavior |
| ContentSettingsPattern primary_pattern = |
| ContentSettingsPattern::FromString("https://*"); |
| ContentSettingsPattern secondary_pattern = |
| ContentSettingsPattern::FromString("*"); |
| settings->SetContentSettingCustomScope(primary_pattern, secondary_pattern, |
| ContentSettingsType::SOUND, |
| CONTENT_SETTING_BLOCK); |
| EXPECT_TRUE(IsSiteMuted(*tabstrip(), 0)); |
| |
| // Validate we are able to unmute the site (with the wildcard custom behavior) |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_TRUE(IsSiteInContentSettingExceptionList(settings, url, |
| ContentSettingsType::SOUND)); |
| |
| // Validate we are able to mute the site |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_TRUE(IsSiteMuted(*tabstrip(), 0)); |
| |
| // Validate we are able to unmute the site |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_TRUE(IsSiteInContentSettingExceptionList(settings, url, |
| ContentSettingsType::SOUND)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, ToggleSiteMutedWithOtherDisjointRule) { |
| |
| GURL url("https://example.com/"); |
| HostContentSettingsMap* settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| |
| std::unique_ptr<WebContents> new_tab_contents = CreateWebContents(); |
| content::WebContentsTester::For(new_tab_contents.get()) |
| ->SetLastCommittedURL(url); |
| |
| tabstrip()->AddWebContents(std::move(new_tab_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| // Validate if the mute site menu item shows up and the site is unmuted |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandToggleSiteMuted)); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| |
| // Add a wildcard to mute all HTTPS sites as a custom behavior |
| ContentSettingsPattern primary_pattern = |
| ContentSettingsPattern::FromString("https://www.google.com"); |
| ContentSettingsPattern secondary_pattern = |
| ContentSettingsPattern::FromString("*"); |
| settings->SetContentSettingCustomScope(primary_pattern, secondary_pattern, |
| ContentSettingsType::SOUND, |
| CONTENT_SETTING_BLOCK); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| |
| // Validate we are able to mute the site |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_TRUE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_TRUE(IsSiteInContentSettingExceptionList(settings, url, |
| ContentSettingsType::SOUND)); |
| |
| // Validate we are able to unmute the site |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_FALSE(IsSiteInContentSettingExceptionList(settings, url, |
| ContentSettingsType::SOUND)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, ToggleSiteMutedWithDifferentDefault) { |
| |
| GURL url("https://example.com/"); |
| HostContentSettingsMap* settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| |
| std::unique_ptr<WebContents> new_tab_contents = CreateWebContents(); |
| content::WebContentsTester::For(new_tab_contents.get()) |
| ->SetLastCommittedURL(url); |
| |
| tabstrip()->AddWebContents(std::move(new_tab_contents), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| |
| settings->SetDefaultContentSetting(ContentSettingsType::SOUND, |
| ContentSetting::CONTENT_SETTING_BLOCK); |
| |
| // Validate if the mute site menu item shows up and the site is muted |
| EXPECT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandToggleSiteMuted)); |
| EXPECT_TRUE(IsSiteMuted(*tabstrip(), 0)); |
| |
| // Validate if toggling the state successfully unmutes the site |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_TRUE(IsSiteInContentSettingExceptionList(settings, url, |
| ContentSettingsType::SOUND)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, ToggleMuteUnmuteMultipleSites) { |
| |
| GURL url1("https://example1.com/"); |
| std::unique_ptr<WebContents> new_tab_contents1 = CreateWebContents(); |
| content::WebContentsTester::For(new_tab_contents1.get()) |
| ->SetLastCommittedURL(url1); |
| tabstrip()->AddWebContents(std::move(new_tab_contents1), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| |
| GURL url2("https://example2.com/"); |
| std::unique_ptr<WebContents> new_tab_contents2 = CreateWebContents(); |
| content::WebContentsTester::For(new_tab_contents2.get()) |
| ->SetLastCommittedURL(url2); |
| tabstrip()->AddWebContents(std::move(new_tab_contents2), -1, |
| ui::PAGE_TRANSITION_TYPED, |
| AddTabTypes::ADD_ACTIVE); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 1)); |
| |
| tabstrip()->SelectTabAt(0); |
| EXPECT_TRUE(tabstrip()->selection_model().IsSelected(1)); |
| |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_TRUE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_TRUE(IsSiteMuted(*tabstrip(), 1)); |
| |
| tabstrip()->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandToggleSiteMuted); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 0)); |
| EXPECT_FALSE(IsSiteMuted(*tabstrip(), 1)); |
| |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| TEST_F(TabStripModelTest, AppendTab) { |
| MockBrowserWindowInterface bwi; |
| delegate()->SetBrowserWindowInterface(&bwi); |
| ON_CALL(bwi, GetTabStripModel()).WillByDefault(::testing::Return(tabstrip())); |
| ASSERT_TRUE(tabstrip()->empty()); |
| |
| // Create a 2 tabs to serve as an opener and the previous opener. |
| PrepareTabs(tabstrip(), 4); |
| ASSERT_EQ(4, tabstrip()->count()); |
| |
| // Force the opener of tab in index 1 to be tab at index 0. |
| tabstrip()->SetOpenerOfWebContentsAt(1, tabstrip()->GetWebContentsAt(0)); |
| ASSERT_EQ(tabstrip()->GetTabAtIndex(0), tabstrip()->GetOpenerOfTabAt(1)); |
| |
| // Detach 2 tabs for the test, one for each option. |
| std::unique_ptr<tabs::TabModel> tab_model_with_foreground_true = |
| tabstrip()->DetachTabAtForInsertion(2); |
| tabs::TabInterface* tab_with_foreground_true_ptr = |
| tab_model_with_foreground_true.get(); |
| ASSERT_EQ(3, tabstrip()->count()); |
| // Tabs not in a tabstrip are not supported - this should CHECK. |
| ASSERT_DEATH_IF_SUPPORTED( |
| tab_with_foreground_true_ptr->GetBrowserWindowInterface(), ""); |
| |
| std::unique_ptr<tabs::TabModel> tab_model_with_foreground_false = |
| tabstrip()->DetachTabAtForInsertion(2); |
| tabs::TabInterface* tab_with_foreground_false_ptr = |
| tab_model_with_foreground_false.get(); |
| ASSERT_EQ(2, tabstrip()->count()); |
| // Tabs not in a tabstrip are not supported - this should CHECK. |
| ASSERT_DEATH_IF_SUPPORTED( |
| tab_with_foreground_true_ptr->GetBrowserWindowInterface(), ""); |
| |
| // Add a 3rd tab using the foreground option. When the foreground option is |
| // used, the new tab should become active, and the previous tab should become |
| // the opener for the newly active tab. |
| tabstrip()->AppendTab(std::move(tab_model_with_foreground_true), |
| /*foreground=*/true); |
| EXPECT_TRUE(tabstrip()->ContainsIndex(2)); |
| EXPECT_EQ(2, tabstrip()->active_index()); |
| EXPECT_EQ(tabstrip()->GetTabAtIndex(1), tabstrip()->GetOpenerOfTabAt(2)); |
| EXPECT_EQ(tab_with_foreground_true_ptr->GetBrowserWindowInterface() |
| ->GetTabStripModel(), |
| tabstrip()); |
| |
| // Add a 4th tab using the non foreground option. this is similar to using the |
| // AddType NONE, which should not set the active tab and should not inherit |
| // the opener. |
| tabstrip()->AppendTab(std::move(tab_model_with_foreground_false), |
| /*foreground=*/false); |
| EXPECT_TRUE(tabstrip()->ContainsIndex(3)); |
| EXPECT_EQ(2, tabstrip()->active_index()); |
| EXPECT_EQ(nullptr, tabstrip()->GetOpenerOfTabAt(3)); |
| EXPECT_EQ(tab_with_foreground_false_ptr->GetBrowserWindowInterface() |
| ->GetTabStripModel(), |
| tabstrip()); |
| |
| delegate()->SetBrowserWindowInterface(nullptr); |
| } |
| |
| TEST_F(TabStripModelTest, SelectionChangedSingleOperationObserverTest) { |
| // Add 4 tabs to the tabstrip model |
| PrepareTabs(tabstrip(), 4); |
| ASSERT_EQ(4, tabstrip()->count()); |
| tabstrip()->ActivateTabAt(0); |
| |
| // Check selection change after insertion. |
| tabstrip()->InsertWebContentsAt(0, CreateWebContentsWithID(5), |
| AddTabTypes::ADD_NONE); |
| ObservedSelectionChange change = observer()->GetLatestSelectionChange(); |
| |
| // Active webcontents and selection list should not change. |
| EXPECT_EQ(change.old_tab, tabstrip()->GetTabAtIndex(1)->GetHandle()); |
| EXPECT_EQ(change.new_tab, tabstrip()->GetTabAtIndex(1)->GetHandle()); |
| |
| EXPECT_EQ(change.old_model.active(), 1u); |
| EXPECT_EQ(change.old_model.size(), 1u); |
| |
| EXPECT_EQ(change.new_model.active(), 1u); |
| EXPECT_EQ(change.new_model.size(), 1u); |
| |
| // Check selection change after move. |
| tabstrip()->MoveWebContentsAt(0, 2, false); |
| change = observer()->GetLatestSelectionChange(); |
| |
| // Active webcontents should not change but selection list changes. |
| EXPECT_EQ(change.old_tab, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| EXPECT_EQ(change.new_tab, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| |
| EXPECT_EQ(change.old_model.active(), 1u); |
| EXPECT_EQ(change.old_model.size(), 1u); |
| |
| EXPECT_EQ(change.new_model.active(), 0u); |
| EXPECT_EQ(change.new_model.size(), 1u); |
| |
| // Check selection change after move with select_after_move as true. |
| tabstrip()->MoveWebContentsAt(1, 0, true); |
| change = observer()->GetLatestSelectionChange(); |
| |
| EXPECT_EQ(change.old_tab, tabstrip()->GetTabAtIndex(1)->GetHandle()); |
| EXPECT_EQ(change.new_tab, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| |
| EXPECT_EQ(change.old_model.active(), 0u); |
| EXPECT_EQ(change.old_model.size(), 1u); |
| |
| EXPECT_EQ(change.new_model.active(), 0u); |
| EXPECT_EQ(change.new_model.size(), 1u); |
| |
| observer()->ClearStates(); |
| } |
| |
| TEST_F(TabStripModelTest, SelectionChangedForMoveGroupWithSelectedTab) { |
| |
| // Add 6 tabs to the tabstrip model. |
| PrepareTabs(tabstrip(), 6); |
| ASSERT_EQ(6, tabstrip()->count()); |
| tabstrip()->ActivateTabAt(2); |
| |
| // Group tabs 1, 2, and 3 into a tab group. |
| tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({1, 2, 3}); |
| ASSERT_EQ(group, tabstrip()->GetTabAtIndex(1)->GetGroup().value()); |
| |
| tabstrip()->SelectTabAt(0); |
| |
| // Verify the selection model before moving the group. |
| EXPECT_TRUE(tabstrip()->selection_model().IsSelected(2)); |
| EXPECT_EQ(tabstrip()->selection_model().active(), 0u); |
| |
| // Check selection change after moving the group. |
| tabstrip()->MoveGroupTo(group, 2); |
| ObservedSelectionChange change = observer()->GetLatestSelectionChange(); |
| |
| // Active webcontents should not change and selection model updates. |
| EXPECT_EQ(change.old_tab, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| EXPECT_EQ(change.new_tab, tabstrip()->GetTabAtIndex(0)->GetHandle()); |
| |
| EXPECT_EQ(change.old_model.size(), 2u); |
| |
| EXPECT_EQ(change.new_model.active(), 0u); |
| EXPECT_TRUE(change.new_model.IsSelected(3)); |
| EXPECT_EQ(change.new_model.size(), 2u); |
| |
| observer()->ClearStates(); |
| } |
| |
| TEST_F(TabStripModelTest, SelectionChangedForMoveSelectedTabsTo) { |
| // Add 6 tabs to the tabstrip model. |
| PrepareTabs(tabstrip(), 6); |
| ASSERT_EQ(6, tabstrip()->count()); |
| tabstrip()->ActivateTabAt(2); |
| |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->SelectTabAt(2); |
| tabstrip()->SelectTabAt(4); |
| |
| // Verify the selection model before moving the tabs. |
| EXPECT_TRUE(tabstrip()->selection_model().IsSelected(0)); |
| EXPECT_TRUE(tabstrip()->selection_model().IsSelected(2)); |
| EXPECT_TRUE(tabstrip()->selection_model().IsSelected(4)); |
| EXPECT_EQ(tabstrip()->selection_model().active(), 4u); |
| |
| // Move the selected tabs to index 3. |
| tabstrip()->MoveSelectedTabsTo(3, std::nullopt); |
| ObservedSelectionChange change = observer()->GetLatestSelectionChange(); |
| |
| // Active webcontents and selection model should update correctly. |
| EXPECT_EQ(change.old_tab, tabstrip()->GetTabAtIndex(5)->GetHandle()); |
| EXPECT_EQ(change.new_tab, tabstrip()->GetTabAtIndex(5)->GetHandle()); |
| |
| EXPECT_EQ(change.old_model.size(), 3u); |
| |
| EXPECT_EQ(change.new_model.active(), 5u); |
| EXPECT_TRUE(change.new_model.IsSelected(3)); |
| EXPECT_TRUE(change.new_model.IsSelected(4)); |
| EXPECT_TRUE(change.new_model.IsSelected(5)); |
| EXPECT_EQ(change.new_model.size(), 3u); |
| |
| observer()->ClearStates(); |
| } |
| |
| TEST_F(TabStripModelTest, AddToComparisonTable_EnabledForHttps) { |
| std::unique_ptr<content::WebContents> https_web_contents = |
| content::WebContentsTester::CreateTestWebContents(profile(), nullptr); |
| content::WebContentsTester::For(https_web_contents.get()) |
| ->NavigateAndCommit(GURL("https://example.com")); |
| |
| tabstrip()->AppendWebContents(std::move(https_web_contents), true); |
| ASSERT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandAddToNewComparisonTable)); |
| ASSERT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandAddToExistingComparisonTable)); |
| } |
| |
| TEST_F(TabStripModelTest, AddToComparisonTable_EnabledForHttp) { |
| std::unique_ptr<content::WebContents> http_web_contents = |
| content::WebContentsTester::CreateTestWebContents(profile(), nullptr); |
| content::WebContentsTester::For(http_web_contents.get()) |
| ->NavigateAndCommit(GURL("http://example.com")); |
| |
| tabstrip()->AppendWebContents(std::move(http_web_contents), true); |
| ASSERT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandAddToNewComparisonTable)); |
| ASSERT_TRUE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandAddToExistingComparisonTable)); |
| } |
| |
| TEST_F(TabStripModelTest, AddToComparisonTable_DisabledForNonHttpOrHttps) { |
| std::unique_ptr<content::WebContents> chrome_web_contents = |
| content::WebContentsTester::CreateTestWebContents(profile(), nullptr); |
| content::WebContentsTester::For(chrome_web_contents.get()) |
| ->NavigateAndCommit(GURL("chrome://abc")); |
| |
| tabstrip()->AppendWebContents(std::move(chrome_web_contents), true); |
| ASSERT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandAddToNewComparisonTable)); |
| ASSERT_FALSE(tabstrip()->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandAddToExistingComparisonTable)); |
| } |
| |
| TEST_F(TabStripModelTest, AddToComparisonTable_AddToNewTableOpensTab) { |
| GURL url("https://example.com"); |
| |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContentsTester::CreateTestWebContents(profile(), nullptr); |
| content::WebContentsTester::For(web_contents.get())->NavigateAndCommit(url); |
| tabstrip()->AppendWebContents(std::move(web_contents), true); |
| |
| tabstrip()->ExecuteContextMenuCommand( |
| 0, TabStripModel::CommandAddToNewComparisonTable); |
| |
| // New Compare tab with the URL should be opened and activated. |
| ASSERT_TRUE(tabstrip()->count() == 2); |
| ASSERT_TRUE(tabstrip()->GetActiveWebContents()->GetVisibleURL() == |
| commerce::GetProductSpecsTabUrl({url})); |
| } |
| |
| TEST_F(TabStripModelTest, ExtendSelectionTo_SplitTabs) { |
| |
| // Create six tabs with a split containing tabs 0 and 1 and another split with |
| // tabs 4 and 5. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 0, {0})); |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(4); |
| tabstrip()->AddToNewSplit({5}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0s 1s 2 3 4s 5s", GetTabStripStateString(tabstrip())); |
| |
| // When active tab is part of a split, the selection should cover the split. |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->ExtendSelectionTo(2); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2}); |
| |
| // When index tab is part of a split and selection extends left, the selection |
| // should cover the split. |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->ExtendSelectionTo(1); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2}); |
| |
| // When index tab is part of a split and selection extends right, the |
| // selection should cover the split. |
| tabstrip()->ActivateTabAt(3); |
| tabstrip()->ExtendSelectionTo(4); |
| ExpectSelectionIsExactly(tabstrip(), {3, 4, 5}); |
| } |
| |
| TEST_F(TabStripModelTest, ExtendSelectionTo_MultipleInDifferentDirection) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| tabstrip()->ActivateTabAt(2); |
| |
| tabstrip()->ExtendSelectionTo(4); |
| ExpectSelectionIsExactly(tabstrip(), {2, 3, 4}); |
| EXPECT_EQ(tabstrip()->active_index(), 4); |
| |
| tabstrip()->ExtendSelectionTo(0); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2}); |
| EXPECT_EQ(tabstrip()->active_index(), 0); |
| } |
| |
| TEST_F(TabStripModelTest, ExtendSelectionTo_MultipleInSameDirection) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {0})); |
| tabstrip()->ActivateTabAt(0); |
| |
| tabstrip()->ExtendSelectionTo(1); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1}); |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| |
| tabstrip()->ExtendSelectionTo(2); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2}); |
| EXPECT_EQ(tabstrip()->active_index(), 2); |
| } |
| |
| TEST_F(TabStripModelTest, AddSelectionFromAnchorTo_SplitTabs) { |
| |
| // Create six tabs with a split containing tabs 0 and 1 and another split with |
| // tabs 4 and 5. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 0, {0})); |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(4); |
| tabstrip()->AddToNewSplit({5}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| tabstrip()->ActivateTabAt(3); |
| tabstrip()->AddSelectionFromAnchorTo(4); |
| ExpectSelectionIsExactly(tabstrip(), {3, 4, 5}); |
| EXPECT_EQ(tabstrip()->active_index(), 4); |
| |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->ExtendSelectionTo(1); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2}); |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| } |
| |
| TEST_F(TabStripModelTest, |
| AddSelectionFromAnchorTo_MultipleInDifferentDirection) { |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {2})); |
| tabstrip()->ActivateTabAt(2); |
| |
| tabstrip()->AddSelectionFromAnchorTo(4); |
| ExpectSelectionIsExactly(tabstrip(), {2, 3, 4}); |
| EXPECT_EQ(tabstrip()->active_index(), 4); |
| |
| tabstrip()->AddSelectionFromAnchorTo(0); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2, 3, 4}); |
| EXPECT_EQ(tabstrip()->active_index(), 0); |
| } |
| |
| TEST_F(TabStripModelTest, AddSelectionFromAnchorTo_NoAnchorAndSplit) { |
| |
| // Create six tabs with a split containing tabs 0 and 1. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 6, 0, {0})); |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| ui::ListSelectionModel selection_model; |
| selection_model.AddIndexToSelection(3); |
| selection_model.set_anchor(std::nullopt); |
| selection_model.set_active(3); |
| tabstrip()->SetSelectionFromModel(selection_model); |
| ExpectSelectionIsExactly(tabstrip(), {3}); |
| tabstrip()->AddSelectionFromAnchorTo(1); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1}); |
| } |
| |
| TEST_F(TabStripModelTest, SelectTabAt_SplitTabs) { |
| |
| // Create four tabs with a split containing tabs 2 and 3. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 0, {0})); |
| tabstrip()->ActivateTabAt(3); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0 1 2s 3s", GetTabStripStateString(tabstrip())); |
| |
| // When selected tab is part of a split, both tabs in the split are selected. |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->SelectTabAt(2); |
| ExpectSelectionIsExactly(tabstrip(), {1, 2, 3}); |
| } |
| |
| TEST_F(TabStripModelTest, DeselectTabAt_SplitTabs) { |
| |
| // Create four tabs with a split containing tabs 2 and 3. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 0, {0})); |
| tabstrip()->ActivateTabAt(3); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0 1 2s 3s", GetTabStripStateString(tabstrip())); |
| |
| // When deselected tab is part of a split, both tabs in the split are |
| // deselected. |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->SelectTabAt(2); |
| tabstrip()->DeselectTabAt(2); |
| ExpectSelectionIsExactly(tabstrip(), {0}); |
| } |
| |
| TEST_F(TabStripModelTest, DeselectTabAt_CantDeselectOnlySelectedSplitTabs) { |
| |
| // Create four tabs with a split containing tabs 2 and 3. |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tabstrip(), 4, 0, {0})); |
| tabstrip()->ActivateTabAt(3); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_EQ("0 1 2s 3s", GetTabStripStateString(tabstrip())); |
| |
| // When deselected part of a split tab when no other tabs are selected, |
| // nothing happens. |
| tabstrip()->ActivateTabAt(2); |
| tabstrip()->DeselectTabAt(2); |
| ExpectSelectionIsExactly(tabstrip(), {2, 3}); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveSplitInSelectionActivatesRemainingTab) { |
| // Add 6 tabs to the tabstrip model. The first 4 tabs will be selected with |
| // the tab at index 1 as the active tab. Tabs 1 and 2 are in a split view. |
| PrepareTabs(tabstrip(), 6); |
| ASSERT_EQ(6, tabstrip()->count()); |
| tabstrip()->ActivateTabAt(1); |
| tabstrip()->AddToNewSplit({2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tabstrip()->ActivateTabAt(3); |
| tabstrip()->SelectTabAt(0); |
| tabstrip()->SelectTabAt(2); |
| tabstrip()->SelectTabAt(1); |
| |
| // Verify the selection model before closing the tab. |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2, 3}); |
| |
| // Close the right half of the split tab. |
| tabstrip()->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE); |
| |
| // Verify that the other half of the split is active and 3 tabs are selected. |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1, 2}); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveSplitUnselectsNonActiveTab) { |
| // Add 4 tabs to the tabstrip model. Tabs 1 and 2 are in a split view and |
| // active/selected. |
| PrepareTabs(tabstrip(), 4); |
| ASSERT_EQ(4, tabstrip()->count()); |
| tabstrip()->ActivateTabAt(1); |
| split_tabs::SplitTabId split_tab_id = tabstrip()->AddToNewSplit( |
| {2}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // Verify the selection model before closing the tab. |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| ExpectSelectionIsExactly(tabstrip(), {1, 2}); |
| |
| // Unsplit the tabs |
| tabstrip()->RemoveSplit(split_tab_id); |
| |
| // Verify that only the active tab (1) is selected. |
| EXPECT_EQ(tabstrip()->active_index(), 1); |
| ExpectSelectionIsExactly(tabstrip(), {1}); |
| } |
| |
| TEST_F(TabStripModelTest, SplitSelectionTestFromModel) { |
| TestTabStripModelDelegate delegate; |
| TabStripModel tabstrip(&delegate, profile()); |
| EXPECT_TRUE(tabstrip.empty()); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(&tabstrip, 10, 5, {2, 3, 6, 7})); |
| |
| tabstrip.ActivateTabAt(6, |
| TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| tabstrip.AddToNewSplit({7}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| tabstrip.ActivateTabAt(2, |
| TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| tabstrip.AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // Pass in only one of the selected tabs in the splits. |
| PrepareTabstripForSelectionTest(&tabstrip, 0, 0, {6, 2}); |
| ui::ListSelectionModel::SelectedIndices expected_sel_indices; |
| expected_sel_indices.insert(6); |
| expected_sel_indices.insert(7); |
| expected_sel_indices.insert(1); |
| expected_sel_indices.insert(2); |
| |
| EXPECT_EQ(tabstrip.selection_model().selected_indices(), |
| expected_sel_indices); |
| } |
| |
| TEST_F(TabStripModelTest, RemoveLeftTabInSplitActivatesRemainingTab) { |
| // Add 4 tabs to the tabstrip model. Tabs 0 and 1 are in a split view. |
| PrepareTabs(tabstrip(), 4); |
| ASSERT_EQ(4, tabstrip()->count()); |
| tabstrip()->ActivateTabAt(0); |
| tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // Verify the selection model before closing the tab. |
| EXPECT_EQ("0s 1s 2 3", GetTabStripStateString(tabstrip())); |
| EXPECT_EQ(tabstrip()->active_index(), 0); |
| ExpectSelectionIsExactly(tabstrip(), {0, 1}); |
| |
| // Close the left half of the split tab. |
| tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_NONE); |
| |
| // Verify that the other half of the split is now active. |
| EXPECT_EQ(tabstrip()->active_index(), 0); |
| ExpectSelectionIsExactly(tabstrip(), {0}); |
| } |
| |
| TEST_F(TabStripModelTest, IteratorTest) { |
| // Get the iterator without any tabs being present. |
| TabStripModel::TabIterator it = tabstrip()->begin(); |
| EXPECT_EQ(*it, nullptr); |
| EXPECT_EQ(it, tabstrip()->end()); |
| |
| // Add only one unpinned tab |
| PrepareTabstripForSelectionTest(tabstrip(), 1, 0, {0}); |
| it = tabstrip()->begin(); |
| EXPECT_EQ(*it, tabstrip()->GetTabAtIndex(0)); |
| EXPECT_EQ(++it, tabstrip()->end()); |
| |
| // Add 4 tabs to the tabstrip model. Tabs 0 and 1 are in a split view. |
| PrepareTabstripForSelectionTest(tabstrip(), 50, 10, {0}); |
| |
| tabstrip()->AddToNewGroup({15, 16}); |
| tabstrip()->AddToNewGroup({25, 26}); |
| tabstrip()->AddToNewGroup({40}); |
| |
| it = tabstrip()->begin(); |
| int i = 0; |
| while (it != tabstrip()->end()) { |
| EXPECT_EQ(*it, tabstrip()->GetTabAtIndex(i++)); |
| it++; |
| } |
| EXPECT_EQ(i, tabstrip()->count()); |
| } |
| |
| TEST_F(TabStripModelTest, IteratorTestPinnedTab) { |
| // Add only one unpinned tab |
| PrepareTabstripForSelectionTest(tabstrip(), 10, 10, {0}); |
| |
| TabStripModel::TabIterator it = tabstrip()->begin(); |
| int i = 0; |
| while (it != tabstrip()->end()) { |
| EXPECT_EQ(*it, tabstrip()->GetTabAtIndex(i++)); |
| it++; |
| } |
| EXPECT_EQ(i, tabstrip()->count()); |
| } |
| |
| TEST_F(TabStripModelTest, IteratorTestGroupOnlyTabs) { |
| // Add only one unpinned tab |
| PrepareTabstripForSelectionTest(tabstrip(), 5, 0, {0}); |
| tabstrip()->AddToNewGroup({0, 1, 2, 3, 4}); |
| |
| TabStripModel::TabIterator it = tabstrip()->begin(); |
| int i = 0; |
| while (it != tabstrip()->end()) { |
| EXPECT_EQ(*it, tabstrip()->GetTabAtIndex(i++)); |
| it++; |
| } |
| EXPECT_EQ(i, tabstrip()->count()); |
| } |
| |
| // A TestTabStripModelDelegate that allows controlling when the group |
| // destruction callback is run. |
| class CallbackControllingTabStripModelDelegate |
| : public TestTabStripModelDelegate { |
| public: |
| CallbackControllingTabStripModelDelegate() = default; |
| |
| void OnGroupsDestruction(const std::vector<tab_groups::TabGroupId>& group_ids, |
| base::OnceCallback<void()> close_callback, |
| bool delete_groups) override { |
| callback_ = std::move(close_callback); |
| } |
| |
| void RunCallback() { |
| if (callback_) { |
| std::move(callback_).Run(); |
| } |
| } |
| |
| private: |
| base::OnceCallback<void()> callback_; |
| }; |
| |
| class TabStripModelCallbackTest : public testing::Test { |
| public: |
| TabStripModelCallbackTest() |
| : profile_(std::make_unique<TestingProfile>()), |
| delegate_( |
| std::make_unique<CallbackControllingTabStripModelDelegate>()) { |
| ON_CALL(bwi_, GetTabStripModel()) |
| .WillByDefault(::testing::Return(tabstrip_.get())); |
| ON_CALL(bwi_, GetProfile()) |
| .WillByDefault(::testing::Return(profile_.get())); |
| delegate_->SetBrowserWindowInterface(&bwi_); |
| tabstrip_ = |
| std::make_unique<TabStripModel>(delegate_.get(), profile_.get()); |
| } |
| |
| std::unique_ptr<content::WebContents> CreateWebContents() { |
| return content::WebContentsTester::CreateTestWebContents(profile_.get(), |
| nullptr); |
| } |
| |
| TabStripModel* tabstrip() { return tabstrip_.get(); } |
| CallbackControllingTabStripModelDelegate* delegate() { |
| return delegate_.get(); |
| } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| content::RenderViewHostTestEnabler rvh_test_enabler_; |
| std::unique_ptr<TestingProfile> profile_; |
| testing::NiceMock<MockBrowserWindowInterface> bwi_; |
| std::unique_ptr<CallbackControllingTabStripModelDelegate> delegate_; |
| const tabs::TabModel::PreventFeatureInitializationForTesting prevent_; |
| std::unique_ptr<TabStripModel> tabstrip_; |
| }; |
| |
| // Tests that if a group or tab is removed before a command is executed, the |
| // A tab from a group is moved to a new group, but the tab is closed before the |
| // move is complete. |
| TEST_F(TabStripModelCallbackTest, MoveTabToNewGroupThenCloseTab) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| tabstrip()->SelectTabAt(0); |
| tabstrip()->ExecuteContextMenuCommand( |
| 0, TabStripModel::CommandAddToNewGroupFromMenuItem); |
| tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_NONE); |
| delegate()->RunCallback(); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 0u); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // A tab from a group is moved to another group, but the tab is closed before |
| // the move is complete. |
| TEST_F(TabStripModelCallbackTest, MoveTabToExistingGroupThenCloseTab) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| tab_groups::TabGroupId group2_id = tabstrip()->AddToNewGroup({1}); |
| tabstrip()->SelectTabAt(0); |
| tabstrip()->ExecuteAddToExistingGroupCommand(0, group2_id); |
| tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_NONE); |
| delegate()->RunCallback(); |
| EXPECT_EQ( |
| tabstrip()->group_model()->GetTabGroup(group2_id)->ListTabs().length(), |
| 1u); |
| tabstrip()->CloseAllTabs(); |
| } |
| |
| // A tab is moved to a group, but the group is deleted before the move is |
| // complete. |
| TEST_F(TabStripModelCallbackTest, MoveTabToGroupThenDeleteGroup) { |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AppendWebContents(CreateWebContents(), true); |
| tabstrip()->AddToNewGroup({0}); |
| tab_groups::TabGroupId group2_id = tabstrip()->AddToNewGroup({1}); |
| tabstrip()->SelectTabAt(0); |
| tabstrip()->ExecuteAddToExistingGroupCommand(0, group2_id); |
| tabstrip()->CloseAllTabsInGroup(group2_id); |
| delegate()->RunCallback(); |
| EXPECT_EQ(tabstrip()->group_model()->ListTabGroups().size(), 0u); |
| tabstrip()->CloseAllTabs(); |
| } |