|  | // Copyright 2018 The Chromium Authors. All rights reserved. | 
|  | // 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_strip_model.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::TimeDelta::FromSeconds(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, | 
|  | TabStripModel::CloseTypes::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, {TabStripModel::GestureType::kOther}); | 
|  |  | 
|  | clock()->Advance(kTimeStep); | 
|  |  | 
|  | EXPECT_CALL(cb, Run(&model, kTimeStep)).Times(1); | 
|  | CloseTabAt(&model, 0); | 
|  |  | 
|  | tracker.RemoveTabStripModel(&model); | 
|  |  | 
|  | model.DetachWebContentsAt(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, {TabStripModel::GestureType::kOther}); | 
|  |  | 
|  | clock()->Advance(kTimeStep); | 
|  |  | 
|  | model.ActivateTabAt(1, {TabStripModel::GestureType::kOther}); | 
|  | model.ActivateTabAt(0, {TabStripModel::GestureType::kOther}); | 
|  |  | 
|  | EXPECT_CALL(cb, Run(&model, base::TimeDelta())).Times(1); | 
|  | CloseTabAt(&model, 0); | 
|  |  | 
|  | tracker.RemoveTabStripModel(&model); | 
|  |  | 
|  | model.DetachWebContentsAt(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, {TabStripModel::GestureType::kOther}); | 
|  |  | 
|  | EXPECT_CALL(cb, Run(_, _)).Times(0); | 
|  | CloseTabAt(&model, 1); | 
|  |  | 
|  | tracker.RemoveTabStripModel(&model); | 
|  |  | 
|  | model.DetachWebContentsAt(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, {TabStripModel::GestureType::kOther}); | 
|  |  | 
|  | clock()->Advance(kTimeStep); | 
|  | model_2.ActivateTabAt(0, {TabStripModel::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.DetachWebContentsAt(0); | 
|  | model_2.DetachWebContentsAt(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.DetachWebContentsAt(0); | 
|  | } |