// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/metrics/model/pageload_foreground_duration_tab_helper.h"

#import <UIKit/UIKit.h>

#import "components/ukm/ios/ukm_url_recorder.h"
#import "components/ukm/test_ukm_recorder.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

namespace {
const char kPageNavigationUkmEvent[] = "MainFrameNavigation";
const char kPageNavigationUkmMetric[] = "DidCommit";
const char kPageForegroundSessionUkmSearchMatchesEvent[] =
    "PageForegroundSession";
}  // namespace

class PageloadForegroundDurationTabHelperTest : public PlatformTest {
 protected:
  PageloadForegroundDurationTabHelperTest() {
    // This must be initialized first so that it is the first receiver of
    // WebStateObserver calls to set the navigation id.
    ukm::InitializeSourceUrlRecorderForWebState(&web_state_);
    PageloadForegroundDurationTabHelper::CreateForWebState(&web_state_);
  }

  base::test::TaskEnvironment environment_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  web::FakeWebState web_state_;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
};

// Tests that a navigation UKM is logged as true after a successful navigation.
TEST_F(PageloadForegroundDurationTabHelperTest,
       VerifyUKMLoggedAfterNavigation) {
  test_ukm_recorder_.Purge();
  web::FakeNavigationContext context_with_zero_nav_id;

  web_state_.WasShown();
  // No entry should be recorded for the interaction above.
  const auto& navigation_entries =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  ASSERT_EQ(0u, navigation_entries.size());

  web::FakeNavigationContext context;
  context.SetIsSameDocument(false);
  context.SetHasCommitted(true);
  web_state_.OnNavigationStarted(&context);
  // No navigation logging have occurred yet.
  const auto& navigation_start_navigation_entries =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  ASSERT_EQ(0u, navigation_start_navigation_entries.size());

  web_state_.OnNavigationFinished(&context);
  // A successful navigation should be recorded
  const auto& post_navigation_entry =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  ASSERT_EQ(1u, post_navigation_entry.size());
  const ukm::mojom::UkmEntry* entry = post_navigation_entry[0];
  ASSERT_TRUE(entry);
  EXPECT_NE(ukm::kInvalidSourceId, entry->source_id);
  test_ukm_recorder_.ExpectEntryMetric(entry, kPageNavigationUkmMetric, true);
}

// Tests that a navigation UKM is not logged if the navigation is within the
// same document.
TEST_F(PageloadForegroundDurationTabHelperTest,
       VerifyUKMNotLoggedAfterSameDocumentNavigation) {
  test_ukm_recorder_.Purge();
  web::FakeNavigationContext context_with_zero_nav_id;

  web_state_.WasShown();
  // No entry should be recorded for the interaction above.
  const auto& navigation_entries =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  ASSERT_EQ(0u, navigation_entries.size());

  web::FakeNavigationContext context;
  context.SetIsSameDocument(true);
  context.SetHasCommitted(true);
  web_state_.OnNavigationStarted(&context);
  // No navigation logging have occurred yet.
  const auto& navigation_start_navigation_entries =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  ASSERT_EQ(0u, navigation_start_navigation_entries.size());

  web_state_.OnNavigationFinished(&context);
  // A same-document navigation should result in no logging.
  const auto& post_navigation_entry =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  EXPECT_EQ(0u, post_navigation_entry.size());
}

// Tests that a UKM is logged after a page that is being shown is hidden.
TEST_F(PageloadForegroundDurationTabHelperTest,
       VerifyUKMLoggedAfterShowAndHide) {
  web::FakeNavigationContext context;
  context.SetIsSameDocument(false);
  context.SetHasCommitted(true);
  web_state_.OnNavigationStarted(&context);
  // Mark WebState as visible without logging a page session.
  web_state_.WasShown();
  web_state_.OnNavigationFinished(&context);
  // There should be no entries yet.
  const auto& page_session_entries = test_ukm_recorder_.GetEntriesByName(
      kPageForegroundSessionUkmSearchMatchesEvent);
  ASSERT_EQ(0u, page_session_entries.size());

  web_state_.WasHidden();
  const auto& after_hidden_page_session_entries =
      test_ukm_recorder_.GetEntriesByName(
          kPageForegroundSessionUkmSearchMatchesEvent);
  EXPECT_EQ(1u, after_hidden_page_session_entries.size());
}

// Tests that a UKM is logged after OnRenderProcessGone is called for a page
// that is being shown.
TEST_F(PageloadForegroundDurationTabHelperTest,
       VerifyUKMLoggedAfterRenderProcessGone) {
  web::FakeNavigationContext context;
  context.SetIsSameDocument(false);
  context.SetHasCommitted(true);
  web_state_.OnNavigationStarted(&context);
  // Mark WebState as visible without logging a page session.
  web_state_.WasShown();
  web_state_.OnNavigationFinished(&context);
  // There should be no entries yet.
  const auto& page_session_entries = test_ukm_recorder_.GetEntriesByName(
      kPageForegroundSessionUkmSearchMatchesEvent);
  ASSERT_EQ(0u, page_session_entries.size());

  web_state_.OnRenderProcessGone();
  const auto& after_render_gone_entries = test_ukm_recorder_.GetEntriesByName(
      kPageForegroundSessionUkmSearchMatchesEvent);
  EXPECT_EQ(1u, after_render_gone_entries.size());
}

// Tests that a UKM is logged after a
// UIApplicationDidEnterBackgroundNotification notification and after a
// successive UIApplicationWillEnterForegroundNotification notification that is
// followed by the Webstate being hidden.
TEST_F(PageloadForegroundDurationTabHelperTest,
       VerifyUKMLoggedAfterBackgroundingAndForegrounding) {
  web::FakeNavigationContext context;
  context.SetIsSameDocument(false);
  context.SetHasCommitted(true);
  web_state_.OnNavigationStarted(&context);
  // Mark WebState as visible without logging a page session.
  web_state_.WasShown();
  web_state_.OnNavigationFinished(&context);

  [[NSNotificationCenter defaultCenter]
      postNotificationName:UIApplicationDidEnterBackgroundNotification
                    object:nil];
  const auto& after_background_entries = test_ukm_recorder_.GetEntriesByName(
      kPageForegroundSessionUkmSearchMatchesEvent);
  ASSERT_EQ(1u, after_background_entries.size());

  [[NSNotificationCenter defaultCenter]
      postNotificationName:UIApplicationWillEnterForegroundNotification
                    object:nil];
  const auto& after_foreground_entries = test_ukm_recorder_.GetEntriesByName(
      kPageForegroundSessionUkmSearchMatchesEvent);
  ASSERT_EQ(1u, after_foreground_entries.size());

  web_state_.WasHidden();
  const auto& after_hidden_page_session_entries =
      test_ukm_recorder_.GetEntriesByName(
          kPageForegroundSessionUkmSearchMatchesEvent);
  EXPECT_EQ(2u, after_hidden_page_session_entries.size());
}

// Tests that no UKMs are logged as long as the WebState is not visible.
TEST_F(PageloadForegroundDurationTabHelperTest,
       VerifyNoUKMLoggedIfWebStateNotVisible) {
  // Mark WebState as not visible.
  web_state_.WasHidden();

  web::FakeNavigationContext context;
  context.SetIsSameDocument(false);
  context.SetHasCommitted(true);
  web_state_.OnNavigationStarted(&context);
  web_state_.OnNavigationFinished(&context);
  const auto& post_navigation_entries =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  EXPECT_EQ(0u, post_navigation_entries.size());

  web_state_.OnNavigationStarted(&context);
  web_state_.OnNavigationFinished(&context);
  const auto& second_post_navigation_entries =
      test_ukm_recorder_.GetEntriesByName(kPageNavigationUkmEvent);
  EXPECT_EQ(0u, second_post_navigation_entries.size());

  [[NSNotificationCenter defaultCenter]
      postNotificationName:UIApplicationDidEnterBackgroundNotification
                    object:nil];
  const auto& after_background_entries = test_ukm_recorder_.GetEntriesByName(
      kPageForegroundSessionUkmSearchMatchesEvent);
  ASSERT_EQ(0u, after_background_entries.size());

  [[NSNotificationCenter defaultCenter]
      postNotificationName:UIApplicationWillEnterForegroundNotification
                    object:nil];
  const auto& after_foreground_entries = test_ukm_recorder_.GetEntriesByName(
      kPageForegroundSessionUkmSearchMatchesEvent);
  ASSERT_EQ(0u, after_foreground_entries.size());

  web_state_.WasHidden();
  const auto& after_hidden_page_session_entries =
      test_ukm_recorder_.GetEntriesByName(
          kPageForegroundSessionUkmSearchMatchesEvent);
  EXPECT_EQ(0u, after_hidden_page_session_entries.size());
}
