chromium/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc

// 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/history_clusters/history_clusters_tab_helper.h"

#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history_clusters/history_clusters_service_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/history/core/test/history_service_test_util.h"
#include "components/history_clusters/core/features.h"
#include "components/history_clusters/core/history_clusters_service_test_api.h"
#include "components/keyed_service/core/service_access_type.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// Used to invoke a callback after `WebContentsDestroyed()` is invoked, but
// before the `WebContents` has been destroyed.
class OnDestroyWebContentsObserver : content::WebContentsObserver {};

class HistoryClustersTabHelperTest : public ChromeRenderViewHostTestHarness {};

// There are multiple events that occur with nondeterministic order:
// - History (w/ N visits): a history navigation occurs,
//   `OnUpdatedHistoryForNavigation()` is invoked, and a history query is made
//   which will (possibly after other events in the timeline) resolve with N
//   visits.
// - History resolve: the history query made above resolves.
// - Copy: the omnibox URL is copied and `OnOmniboxUrlCopied()` is invoked.
// - Expect UKM: UKM begins tracking a navigation and
//   `TagNavigationAsExpectingUkmNavigationComplete()` is invoked.
// - UKM: UKM ends tracking a navigation and `OnUkmNavigationComplete()` is
//   invoked.
// - Destroy: The `WebContents` is destroyed (i.e. the tab is closed) and
//   `WebContentsDestroyed()` is invoked.
// The below tests test different permutations of these events.

// History (w/ 0 visits) -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked but its history request isn't
//     resolved (because either the tab is closed too soon or there are no
//     matching visits).
// 2) `WebContentsDestroyed()` is invoked.
// Then: 0 context annotations should be committed.
TEST_F(HistoryClustersTabHelperTest, NavigationWith0HistoryVisits) {}

// History (w/ 1 visit) -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked and 1 history visit are
//    fetched.
// 2) `WebContentsDestroyed()` is invoked.
// Then: 1 context annotation should be committed.
TEST_F(HistoryClustersTabHelperTest, NavigationWith1HistoryVisits) {}

// History (w/ 1 *mismatched* visit) -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked and 1 history visit is
//    fetched, but it doesn't match the navigation timestamp.
// 2) `WebContentsDestroyed()` is invoked.
// Then: 0 context annotations should be committed.
TEST_F(HistoryClustersTabHelperTest, NavigationWith1MismatchedHistoryVisit) {}

// History (w/ 2 visits) -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked and 2 history visits are
//    fetched.
// 2) `WebContentsDestroyed()` is invoked.
// Then: 1 context annotation should be committed.
TEST_F(HistoryClustersTabHelperTest, NavigationWith2HistoryVisits) {}

// History (w/ 0 visits) -> history (w/ 0 visits) -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked but its history request isn't
//     resolved (because either the tab is closed too soon or there are no
//     matching visits).
// 2) `OnUpdatedHistoryForNavigation()` is invoked but its history request isn't
//     resolved (because either the tab is closed too soon or there are no
//     matching visits).
// 3) `WebContentsDestroyed()` is invoked.
// Then: 0 visits should be committed.
TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith0HistoryVisits) {}

// History (w/ 2 visits) -> history (w/ 2 visits) -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked and 2 history visits are
//    fetched.
// 2) `OnUpdatedHistoryForNavigation()` is invoked and 2 history visits are
//    fetched.
// 3) `WebContentsDestroyed()` is invoked.
// Then: 2 context annotations should be committed.
TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith2HistoryVisits) {}

// For the remaining tests, all navigations will have at least 1 history visit.

// History -> destroy -> history resolve
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `WebContentsDestroyed()` is invoked before the previous history request is
//    resolved.
// Then: 0 context annotations should be committed.
TEST_F(HistoryClustersTabHelperTest, HistoryResolvedAfterDestroy) {}

// History -> history -> history resolve -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `OnUpdatedHistoryForNavigation()` is invoked before the previous history
//    request is resolved.
// 3) `WebContentsDestroyed()` is invoked.
// Then: 2 context annotations should be committed.
TEST_F(HistoryClustersTabHelperTest, HistoryResolvedAfter2ndNavigation) {}

// History -> copy -> history resolve -> history -> history -> copy -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `OnOmniboxUrlCopied()` is invoked before the previous history request is
//    resolved.
// 3) `OnUpdatedHistoryForNavigation()` is invoked.
// 4) `OnUpdatedHistoryForNavigation()` is invoked.
// 5) `OnOmniboxUrlCopied()` is invoked after the previous history request is
//    resolved
// 6) `WebContentsDestroyed()` is invoked.
// Then: 3 context annotations should be committed; the 1st and 3rd should have
//       `omnibox_url_copied` true.
TEST_F(HistoryClustersTabHelperTest, UrlsCopied) {}

// History -> expect UKM -> UKM -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked.
// 3) `OnUkmNavigationComplete()` is invoked.
// 4) `WebContentsDestroyed()` is invoked.
// Then: 1 context annotations committed after step 3 w/ a `page_end_reason`.
TEST_F(HistoryClustersTabHelperTest, NavigationWithUkmBeforeDestroy) {}

// History -> expect UKM -> destroy -> UKM
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked.
// 3) `WebContentsDestroyed()` is invoked.
// 4) `OnUkmNavigationComplete()` is invoked.
// Then: 1 context annotation should be committed after step 4 w/ a
//       `page_end_reason`.
TEST_F(HistoryClustersTabHelperTest, NavigationWithUkmAfterDestroy) {}

// Expect UKM -> history -> UKM -> destroy
// When:
// 1) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked.
// 2) `OnUpdatedHistoryForNavigation()` is invoked.
// 3) `OnUkmNavigationComplete()` is invoked.
// 4) `WebContentsDestroyed()` is invoked.
// Then: 1 context annotation should be committed after step 3 w/ a
//       `page_end_reason`.
TEST_F(HistoryClustersTabHelperTest,
       NavigationAfterUkmExpectAndWithUkmBeforeDestroy) {}

// History -> expect UKM -> UKM -> destroy -> history resolve
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked.
// 3) `OnUkmNavigationComplete()` is invoked.
// 4) `WebContentsDestroyed()` is invoked before the previous history request is
//    resolved.
// Then: 1 context annotation should be committed after step 4 w/ a
// `page_end_reason`.
TEST_F(HistoryClustersTabHelperTest,
       NavigationWithUkmBeforeDestroyAndHistoryResolvedAfterDestroy) {}

// Expect History -> expect UKM 1 -> UKM 1 -> history -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked for the above
//    navigation.
// 3) `OnUkmNavigationComplete()` is invoked for the above navigation.
// 4) `OnUpdatedHistoryForNavigation()` is invoked.
// 5) `WebContentsDestroyed()` is invoked.
// Then: 2 context annotations should be committed after steps 3 and 5; the 1st
//.      should have a `page_end_reason`.
TEST_F(HistoryClustersTabHelperTest,
       TwoNavigationsWith1stUkmBefore2ndNavigation) {}

// Expect History -> Expect UKM 1 -> history -> UKM 1 -> destroy
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked for the above
//    navigation.
// 3) `OnUpdatedHistoryForNavigation()` is invoked.
// 4) `OnUkmNavigationComplete()` is invoked for the 1st navigation.
// 5) `WebContentsDestroyed()` is invoked.
// Then: 2 context annotations should be committed after steps 4 and 5; the 1st
//.      should have a `page_end_reason`.
TEST_F(HistoryClustersTabHelperTest,
       TwoNavigationsWith1stUkmAfter2ndNavigation) {}

// Expect History -> Expect UKM 2 -> history -> destroy -> UKM 2
// When:
// 1) `OnUpdatedHistoryForNavigation()` is invoked.
// 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked for the below
//    navigation.
// 3) `OnUpdatedHistoryForNavigation()` is invoked.
// 4) `WebContentsDestroyed()` is invoked.
// 5) `OnUkmNavigationComplete()` is invoked for the 2nd navigation.
// Then: 2 context annotations should be committed after steps 2 and 5; the 2nd
//.      should have a `page_end_reason`.
TEST_F(HistoryClustersTabHelperTest, TwoNavigations2ndUkmBefore2ndNavigation) {}

}  // namespace