| // Copyright 2018 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/user_education/active_tab_tracker.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/test/mock_callback.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "chrome/browser/ui/tabs/tab_enums.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h" |
| #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/models/list_selection_model.h" |
| |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Mock; |
| |
| namespace { |
| |
| class TestTabStripModelDelegateNoUnloadListener |
| : public TestTabStripModelDelegate { |
| public: |
| TestTabStripModelDelegateNoUnloadListener() = default; |
| ~TestTabStripModelDelegateNoUnloadListener() override = default; |
| |
| bool RunUnloadListenerBeforeClosing(content::WebContents* contents) override { |
| return false; |
| } |
| }; |
| |
| constexpr base::TimeDelta kTimeStep = base::Seconds(1); |
| |
| } // namespace |
| |
| class ActiveTabTrackerTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| TestingProfile::Builder profile_builder; |
| profile_ = profile_builder.Build(); |
| } |
| |
| void AddTab(TabStripModel* model) { |
| std::unique_ptr<content::WebContents> contents = |
| content::WebContents::Create( |
| content::WebContents::CreateParams(profile_.get())); |
| model->AppendWebContents(std::move(contents), true); |
| } |
| |
| void CloseTabAt(TabStripModel* model, int index) { |
| model->CloseWebContentsAt(index, TabCloseTypes::CLOSE_USER_GESTURE); |
| } |
| |
| Profile* profile() { return profile_.get(); } |
| base::SimpleTestTickClock* clock() { return &clock_; } |
| |
| private: |
| // A |BrowserTaskEnvironment| is needed for creating and using |
| // |WebContents|es in a unit test. |
| content::BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<TestingProfile> profile_; |
| base::SimpleTestTickClock clock_; |
| }; |
| |
| TEST_F(ActiveTabTrackerTest, NotifiesOnActiveTabClosed) { |
| TestTabStripModelDelegateNoUnloadListener delegate; |
| TabStripModel model(&delegate, profile()); |
| |
| base::MockCallback<ActiveTabTracker::ActiveTabClosedCallback> cb; |
| ActiveTabTracker tracker(clock(), cb.Get()); |
| tracker.AddTabStripModel(&model); |
| |
| AddTab(&model); |
| AddTab(&model); |
| clock()->Advance(kTimeStep); |
| |
| model.ActivateTabAt(0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| clock()->Advance(kTimeStep); |
| |
| EXPECT_CALL(cb, Run(&model, kTimeStep)).Times(1); |
| CloseTabAt(&model, 0); |
| |
| tracker.RemoveTabStripModel(&model); |
| |
| model.DetachAndDeleteWebContentsAt(0); |
| } |
| |
| TEST_F(ActiveTabTrackerTest, UpdatesTimes) { |
| TestTabStripModelDelegateNoUnloadListener delegate; |
| TabStripModel model(&delegate, profile()); |
| |
| base::MockCallback<ActiveTabTracker::ActiveTabClosedCallback> cb; |
| ActiveTabTracker tracker(clock(), cb.Get()); |
| tracker.AddTabStripModel(&model); |
| |
| AddTab(&model); |
| AddTab(&model); |
| model.ActivateTabAt(0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| clock()->Advance(kTimeStep); |
| |
| model.ActivateTabAt(1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| model.ActivateTabAt(0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| EXPECT_CALL(cb, Run(&model, base::TimeDelta())).Times(1); |
| CloseTabAt(&model, 0); |
| |
| tracker.RemoveTabStripModel(&model); |
| |
| model.DetachAndDeleteWebContentsAt(0); |
| } |
| |
| TEST_F(ActiveTabTrackerTest, IgnoresInactiveTabs) { |
| TestTabStripModelDelegateNoUnloadListener delegate; |
| TabStripModel model(&delegate, profile()); |
| |
| base::MockCallback<ActiveTabTracker::ActiveTabClosedCallback> cb; |
| ActiveTabTracker tracker(clock(), cb.Get()); |
| tracker.AddTabStripModel(&model); |
| |
| AddTab(&model); |
| AddTab(&model); |
| model.ActivateTabAt(0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| EXPECT_CALL(cb, Run(_, _)).Times(0); |
| CloseTabAt(&model, 1); |
| |
| tracker.RemoveTabStripModel(&model); |
| |
| model.DetachAndDeleteWebContentsAt(0); |
| } |
| |
| TEST_F(ActiveTabTrackerTest, TracksMultipleTabStripModels) { |
| TestTabStripModelDelegateNoUnloadListener delegate; |
| TabStripModel model_1(&delegate, profile()); |
| TabStripModel model_2(&delegate, profile()); |
| |
| base::MockCallback<ActiveTabTracker::ActiveTabClosedCallback> cb; |
| ActiveTabTracker tracker(clock(), cb.Get()); |
| tracker.AddTabStripModel(&model_1); |
| tracker.AddTabStripModel(&model_2); |
| |
| AddTab(&model_1); |
| AddTab(&model_1); |
| AddTab(&model_2); |
| AddTab(&model_2); |
| |
| clock()->Advance(kTimeStep); |
| model_1.ActivateTabAt(0, |
| TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| clock()->Advance(kTimeStep); |
| model_2.ActivateTabAt(0, |
| TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| { |
| InSequence seq; |
| EXPECT_CALL(cb, Run(&model_1, kTimeStep)).Times(1); |
| EXPECT_CALL(cb, Run(&model_2, base::TimeDelta())).Times(1); |
| } |
| |
| CloseTabAt(&model_1, 0); |
| CloseTabAt(&model_2, 0); |
| |
| tracker.RemoveTabStripModel(&model_1); |
| tracker.RemoveTabStripModel(&model_2); |
| |
| model_1.DetachAndDeleteWebContentsAt(0); |
| model_2.DetachAndDeleteWebContentsAt(0); |
| } |
| |
| TEST_F(ActiveTabTrackerTest, StopsObservingUponRemove) { |
| TestTabStripModelDelegateNoUnloadListener delegate; |
| TabStripModel model(&delegate, profile()); |
| |
| base::MockCallback<ActiveTabTracker::ActiveTabClosedCallback> cb; |
| ActiveTabTracker tracker(clock(), cb.Get()); |
| tracker.AddTabStripModel(&model); |
| |
| AddTab(&model); |
| AddTab(&model); |
| |
| tracker.RemoveTabStripModel(&model); |
| |
| EXPECT_CALL(cb, Run(_, _)).Times(0); |
| CloseTabAt(&model, 0); |
| |
| model.DetachAndDeleteWebContentsAt(0); |
| } |