chromium/chrome/test/data/webui/settings/safety_hub_page_test.ts

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

// clang-format off
import 'chrome://settings/lazy_load.js';

import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import type {CardInfo, SettingsSafetyHubPageElement} from 'chrome://settings/lazy_load.js';
import {CardState, ContentSettingsTypes, SafeBrowsingSetting, SafetyHubBrowserProxyImpl, SafetyHubEvent} from 'chrome://settings/lazy_load.js';
import type {SettingsPrefsElement} from 'chrome://settings/settings.js';
import {CrSettingsPrefs, LifetimeBrowserProxyImpl, MetricsBrowserProxyImpl, PasswordManagerImpl, PasswordManagerPage, Router, routes, SafetyHubModuleType, SafetyHubSurfaces} from 'chrome://settings/settings.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {isChildVisible} from 'chrome://webui-test/test_util.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';

import {TestLifetimeBrowserProxy} from './test_lifetime_browser_proxy.js';
import {TestSafetyHubBrowserProxy} from './test_safety_hub_browser_proxy.js';
import {TestPasswordManagerProxy} from './test_password_manager_proxy.js';
import {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';

// <if expr="not chromeos_ash">
import {eventToPromise} from 'chrome://webui-test/test_util.js';
// </if>
// clang-format on

suite('SafetyHubPage', function() {
  let testElement: SettingsSafetyHubPageElement;
  let lifetimeBrowserProxy: TestLifetimeBrowserProxy;
  let safetyHubBrowserProxy: TestSafetyHubBrowserProxy;
  let passwordManagerProxy: TestPasswordManagerProxy;
  let metricsBrowserProxy: TestMetricsBrowserProxy;
  let settingsPrefs: SettingsPrefsElement;

  suiteSetup(function() {
    settingsPrefs = document.createElement('settings-prefs');
    return CrSettingsPrefs.initialized;
  });

  const notificationPermissionMockData = [{
    origin: 'www.example.com',
    notificationInfoString: 'About 1 notifications a day',
  }];

  const unusedSitePermissionMockData = [{
    origin: 'www.example.com',
    permissions: [ContentSettingsTypes.CAMERA],
    expiration: '13317004800000000',  // Represents 2023-01-01T00:00:00.
  }];

  const passwordCardMockData: CardInfo = {
    header: '2 compromised passwords',
    subheader: 'You should change these now',
    state: CardState.WARNING,
  };

  const versionCardMockData: CardInfo = {
    header: 'Chrome is up to date',
    subheader: 'Checked just now',
    state: CardState.SAFE,
  };

  const safeBrowsingCardMockData: CardInfo = {
    header: 'Safe Browsing is off',
    subheader: 'An Extension turned off Safe Browsing',
    state: CardState.INFO,
  };

  setup(function() {
    safetyHubBrowserProxy = new TestSafetyHubBrowserProxy();
    safetyHubBrowserProxy.setPasswordCardData(passwordCardMockData);
    safetyHubBrowserProxy.setVersionCardData(versionCardMockData);
    safetyHubBrowserProxy.setSafeBrowsingCardData(safeBrowsingCardMockData);
    SafetyHubBrowserProxyImpl.setInstance(safetyHubBrowserProxy);
    metricsBrowserProxy = new TestMetricsBrowserProxy();
    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);

    passwordManagerProxy = new TestPasswordManagerProxy();
    PasswordManagerImpl.setInstance(passwordManagerProxy);

    lifetimeBrowserProxy = new TestLifetimeBrowserProxy();
    LifetimeBrowserProxyImpl.setInstance(lifetimeBrowserProxy);

    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    testElement = document.createElement('settings-safety-hub-page');
    testElement.prefs = settingsPrefs.prefs!;
    document.body.appendChild(testElement);
    return flushTasks();
  });

  function assertNoRecommendationState(shouldBeVisible: boolean) {
    assertEquals(
        shouldBeVisible, isChildVisible(testElement, '#emptyStateModule'));
    assertEquals(
        shouldBeVisible, isChildVisible(testElement, '#userEducationModule'));
  }

  async function changeSafeBrowsingGeneratedPref(setting: SafeBrowsingSetting) {
    testElement.set('prefs.generated.safe_browsing', {
      value: setting,
      type: chrome.settingsPrivate.PrefType.DICTIONARY,
    });
    assertEquals(setting, testElement.getPref('generated.safe_browsing').value);
    await flushTasks();
  }

  function assertSafeBrowsingCard(newCardData: CardInfo) {
    assertEquals(
        1, safetyHubBrowserProxy.getCallCount('getSafeBrowsingCardData'));
    assertTrue(isChildVisible(testElement, '#safeBrowsing'));
    assertEquals(
        testElement.$.safeBrowsing.shadowRoot!.querySelector('#header')!
            .textContent!.trim(),
        newCardData.header);
    assertEquals(
        testElement.$.safeBrowsing.shadowRoot!.querySelector('#subheader')!
            .textContent!.trim(),
        newCardData.subheader);
  }

  test(
      'No Recommendation State Visibility With Unused Site Permissions Module',
      async function() {
        // The element is visible when there is nothing to review.
        assertNoRecommendationState(true);

        // The element becomes hidden if the is any module that needs attention.
        webUIListenerCallback(
            SafetyHubEvent.UNUSED_PERMISSIONS_MAYBE_CHANGED,
            unusedSitePermissionMockData);
        await flushTasks();
        assertNoRecommendationState(false);

        // Once hidden, it remains hidden as other modules are visible.
        webUIListenerCallback(
            SafetyHubEvent.UNUSED_PERMISSIONS_MAYBE_CHANGED, []);
        await flushTasks();
        assertNoRecommendationState(false);
      });

  test(
      'No Recommendation State Visibility With Notification Permissions Module',
      async function() {
        // The element is visible when there is nothing to review.
        assertNoRecommendationState(true);

        // The element becomes hidden if the is any module that needs attention.
        webUIListenerCallback(
            SafetyHubEvent.NOTIFICATION_PERMISSIONS_MAYBE_CHANGED,
            notificationPermissionMockData);
        await flushTasks();
        assertNoRecommendationState(false);

        // Once hidden, it remains hidden as other modules are visible.
        webUIListenerCallback(
            SafetyHubEvent.NOTIFICATION_PERMISSIONS_MAYBE_CHANGED, []);
        await flushTasks();
        assertNoRecommendationState(false);
      });

  test(
      'No Recommendation State Visibility With Extensions Module',
      async function() {
        // The element is visible when there is nothing to review.
        assertTrue(isChildVisible(testElement, '#emptyStateModule'));

        // The element becomes hidden if the is any module that needs attention.
        webUIListenerCallback(SafetyHubEvent.EXTENSIONS_CHANGED, 1);
        await flushTasks();
        assertFalse(isChildVisible(testElement, '#emptyStateModule'));

        // Returns when extension module goes away.
        webUIListenerCallback(SafetyHubEvent.EXTENSIONS_CHANGED, 0);
        await flushTasks();
        assertTrue(isChildVisible(testElement, '#emptyStateModule'));
      });

  test('Unused Site Permissions Module Visibility', async function() {
    // The element is not visible when there is nothing to review.
    const unusedSitePermissionsElementTag =
        'settings-safety-hub-unused-site-permissions';
    assertFalse(isChildVisible(testElement, unusedSitePermissionsElementTag));

    // The element becomes visible if the list of permissions is no longer
    // empty.
    webUIListenerCallback(
        SafetyHubEvent.UNUSED_PERMISSIONS_MAYBE_CHANGED,
        unusedSitePermissionMockData);
    await flushTasks();
    assertTrue(isChildVisible(testElement, unusedSitePermissionsElementTag));

    // Once visible, it remains visible regardless of list length.
    webUIListenerCallback(SafetyHubEvent.UNUSED_PERMISSIONS_MAYBE_CHANGED, []);
    await flushTasks();
    assertTrue(isChildVisible(testElement, unusedSitePermissionsElementTag));

    webUIListenerCallback(
        SafetyHubEvent.UNUSED_PERMISSIONS_MAYBE_CHANGED,
        unusedSitePermissionMockData);
    await flushTasks();
    assertTrue(isChildVisible(testElement, unusedSitePermissionsElementTag));
  });

  test('Notification Permissions Module Visibility', async function() {
    // The element is not visible when there is nothing to review.
    const notificationPermissionsElementTag =
        'settings-safety-hub-notification-permissions-module';
    assertFalse(isChildVisible(testElement, notificationPermissionsElementTag));

    // The element becomes visible if the list of permissions is no longer
    // empty.
    webUIListenerCallback(
        SafetyHubEvent.NOTIFICATION_PERMISSIONS_MAYBE_CHANGED,
        notificationPermissionMockData);
    await flushTasks();
    assertTrue(isChildVisible(testElement, notificationPermissionsElementTag));

    // Once visible, it remains visible regardless of list length.
    webUIListenerCallback(
        SafetyHubEvent.NOTIFICATION_PERMISSIONS_MAYBE_CHANGED, []);
    await flushTasks();
    assertTrue(isChildVisible(testElement, notificationPermissionsElementTag));

    webUIListenerCallback(
        SafetyHubEvent.NOTIFICATION_PERMISSIONS_MAYBE_CHANGED,
        notificationPermissionMockData);
    await flushTasks();
    assertTrue(isChildVisible(testElement, notificationPermissionsElementTag));
  });

  test('Extensions Module Visibility', async function() {
    // The element is not visible when there is nothing to review.
    assertFalse(
        isChildVisible(testElement, 'settings-safety-hub-extensions-module'));

    // The element becomes visible if the there are extensions to review.
    webUIListenerCallback(SafetyHubEvent.EXTENSIONS_CHANGED, 2);
    await flushTasks();
    assertTrue(
        isChildVisible(testElement, 'settings-safety-hub-extensions-module'));

    // Once visible, it goes away if all extensions are handled.
    webUIListenerCallback(SafetyHubEvent.EXTENSIONS_CHANGED, 0);
    await flushTasks();
    assertFalse(
        isChildVisible(testElement, 'settings-safety-hub-extensions-module'));
  });

  test('Password Card', async function() {
    assertTrue(isChildVisible(testElement, '#passwords'));

    // Card header and subheader should be what the browser proxy provides.
    assertEquals(
        testElement.$.passwords.shadowRoot!.querySelector(
                                               '#header')!.textContent!.trim(),
        passwordCardMockData.header);
    assertEquals(
        testElement.$.passwords.shadowRoot!.querySelector('#subheader')!
            .textContent!.trim(),
        passwordCardMockData.subheader);

    // Check that the card aria role and description are correct.
    assertEquals(testElement.$.passwords.getAttribute('role'), 'link');
    assertEquals(
        testElement.$.passwords.getAttribute('aria-description'),
        testElement.i18n('safetyHubPasswordNavigationAriaLabel'));
  });

  test('Password Card Clicked', async function() {
    testElement.$.passwords.click();

    // Ensure the Password Manager Check-up page is shown.
    const param = await passwordManagerProxy.whenCalled('showPasswordManager');
    assertEquals(PasswordManagerPage.CHECKUP, param);

    // Ensure the card state on click metrics are recorded.
    await safetyHubBrowserProxy.whenCalled('recordSafetyHubInteraction');
    const result =
        await metricsBrowserProxy.whenCalled('recordSafetyHubCardStateClicked');
    assertEquals('Settings.SafetyHub.PasswordsCard.StatusOnClick', result[0]);
    assertEquals(passwordCardMockData.state, result[1]);
  });

  test('Password Card Clicked via Enter', async function() {
    testElement.$.passwords.dispatchEvent(
        new KeyboardEvent('keydown', {key: 'Enter', bubbles: true}));
    // Ensure the Password Manager Check-up page is shown.
    const param = await passwordManagerProxy.whenCalled('showPasswordManager');
    assertEquals(PasswordManagerPage.CHECKUP, param);
  });

  test('Password Card Clicked via Space', async function() {
    testElement.$.passwords.dispatchEvent(
        new KeyboardEvent('keydown', {key: ' ', bubbles: true}));
    // Ensure the Password Manager Check-up page is shown.
    const param = await passwordManagerProxy.whenCalled('showPasswordManager');
    assertEquals(PasswordManagerPage.CHECKUP, param);
  });

  test('Version Card', async function() {
    assertTrue(isChildVisible(testElement, '#version'));

    // Card header and subheader should be what the browser proxy provides.
    assertEquals(
        testElement.$.version.shadowRoot!.querySelector(
                                             '#header')!.textContent!.trim(),
        versionCardMockData.header);
    assertEquals(
        testElement.$.version.shadowRoot!.querySelector(
                                             '#subheader')!.textContent!.trim(),
        versionCardMockData.subheader);

    // Check that the card aria role and description are correct.
    assertEquals(testElement.$.passwords.getAttribute('role'), 'link');
    assertEquals(
        testElement.$.version.getAttribute('aria-description'),
        testElement.i18n('safetyHubVersionNavigationAriaLabel'));
  });

  test('Version Card Clicked When No Update Waiting', async function() {
    testElement.$.version.click();

    // Ensure the About page is shown.
    assertEquals(routes.ABOUT, Router.getInstance().getCurrentRoute());

    // Ensure the card state on click metrics are recorded.
    await safetyHubBrowserProxy.whenCalled('recordSafetyHubInteraction');
    const result =
        await metricsBrowserProxy.whenCalled('recordSafetyHubCardStateClicked');
    assertEquals('Settings.SafetyHub.VersionCard.StatusOnClick', result[0]);
    assertEquals(versionCardMockData.state, result[1]);
  });

  test('Version Card Clicked When Update Waiting', async function() {
    const versionCardMockData: CardInfo = {
      header: 'Chrome is not up to date',
      subheader: 'Relaunch to update',
      state: CardState.WARNING,
    };
    safetyHubBrowserProxy.setVersionCardData(versionCardMockData);
    testElement = document.createElement('settings-safety-hub-page');
    document.body.appendChild(testElement);
    await flushTasks();

    // Check that the card aria role and description are correct.
    assertEquals(testElement.$.version.getAttribute('role'), 'button');
    assertEquals(
        testElement.$.version.getAttribute('aria-description'),
        testElement.i18n('safetyHubVersionRelaunchAriaLabel'));

    // <if expr="not chromeos_ash">
    lifetimeBrowserProxy.setShouldShowRelaunchConfirmationDialog(true);
    lifetimeBrowserProxy.setRelaunchConfirmationDialogDescription(
        'Test description.');
    // </if>

    testElement.$.version.click();

    // <if expr="not chromeos_ash">
    // Ensure the confirmation dialog is always shown.
    await eventToPromise('cr-dialog-open', testElement);
    const relaunchConfirmationDialogElement =
        testElement.shadowRoot!.querySelector('relaunch-confirmation-dialog')!;
    assertTrue(relaunchConfirmationDialogElement.$.dialog.open);

    // Ensure the confirmation dialog shows a correct description.
    const dialog = relaunchConfirmationDialogElement.shadowRoot!.querySelector(
        'cr-dialog')!;
    const description =
        dialog.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=body]')!;
    assertEquals(
        'Test description.', description.assignedNodes()[0]!.textContent);

    relaunchConfirmationDialogElement.$.confirm.click();
    // </if>

    // Ensure the card state on click metrics are recorded.
    await safetyHubBrowserProxy.whenCalled('recordSafetyHubInteraction');
    const result =
        await metricsBrowserProxy.whenCalled('recordSafetyHubCardStateClicked');
    assertEquals('Settings.SafetyHub.VersionCard.StatusOnClick', result[0]);
    assertEquals(versionCardMockData.state, result[1]);

    // Ensure the browser is restarted.
    return lifetimeBrowserProxy.whenCalled('relaunch');
  });

  test('Version Card Clicked via Enter', async function() {
    testElement.$.passwords.dispatchEvent(
        new KeyboardEvent('keydown', {key: 'Enter', bubbles: true}));
    // Ensure the About page is shown.
    assertEquals(routes.ABOUT, Router.getInstance().getCurrentRoute());
  });

  test('Version Card Clicked via Space', async function() {
    testElement.$.passwords.dispatchEvent(
        new KeyboardEvent('keydown', {key: ' ', bubbles: true}));
    // Ensure the About page is shown.
    assertEquals(routes.ABOUT, Router.getInstance().getCurrentRoute());
  });

  test('Safe Browsing Card', async function() {
    assertTrue(isChildVisible(testElement, '#safeBrowsing'));

    // Card header and subheader should be what the browser proxy provides.
    assertEquals(
        testElement.$.safeBrowsing.shadowRoot!.querySelector('#header')!
            .textContent!.trim(),
        safeBrowsingCardMockData.header);
    assertEquals(
        testElement.$.safeBrowsing.shadowRoot!.querySelector('#subheader')!
            .textContent!.trim(),
        safeBrowsingCardMockData.subheader);

    // Check that the card aria role and description are correct.
    assertEquals(testElement.$.passwords.getAttribute('role'), 'link');
    assertEquals(
        testElement.$.safeBrowsing.getAttribute('aria-description'),
        testElement.i18n('safetyHubSBNavigationAriaLabel'));
  });

  test('Safe Browsing Card Clicked', async function() {
    testElement.$.safeBrowsing.click();

    // Ensure the Security Settings page is shown.
    assertEquals(routes.SECURITY, Router.getInstance().getCurrentRoute());

    // Ensure the card state on click metrics are recorded.
    await safetyHubBrowserProxy.whenCalled('recordSafetyHubInteraction');
    const result =
        await metricsBrowserProxy.whenCalled('recordSafetyHubCardStateClicked');
    assertEquals(
        'Settings.SafetyHub.SafeBrowsingCard.StatusOnClick', result[0]);
    assertEquals(safeBrowsingCardMockData.state, result[1]);
  });

  test('Safe Browsing Card Clicked via Enter', async function() {
    testElement.$.passwords.dispatchEvent(
        new KeyboardEvent('keydown', {key: 'Enter', bubbles: true}));
    // Ensure the Security Settings page is shown.
    assertEquals(routes.SECURITY, Router.getInstance().getCurrentRoute());
  });

  test('Safe Browsing Card Clicked via Space', async function() {
    testElement.$.passwords.dispatchEvent(
        new KeyboardEvent('keydown', {key: ' ', bubbles: true}));
    // Ensure the Security Settings page is shown.
    assertEquals(routes.SECURITY, Router.getInstance().getCurrentRoute());
  });

  test(
      `Safe Browsing Card updates upon Safe Browsing settings change`,
      async function() {
        const standardCardData: CardInfo = {
          header: 'Safe Browsing is on',
          subheader: 'You are getting a standard protection.',
          state: CardState.SAFE,
        };

        const enhancedCardData: CardInfo = {
          header: 'Enhanced Safe Browsing is on',
          subheader: 'You are getting an enhanced protection.',
          state: CardState.SAFE,
        };

        const disabledCardData: CardInfo = safeBrowsingCardMockData;

        // Set the generated.safe_browsing pref to STANDARD.
        safetyHubBrowserProxy.setSafeBrowsingCardData(standardCardData);
        await changeSafeBrowsingGeneratedPref(SafeBrowsingSetting.STANDARD);

        // Change the generated.safe_browsing pref to ENHANCED and check that it
        // triggers getSafeBrowsingCardData call and UI change.
        safetyHubBrowserProxy.resetResolver('getSafeBrowsingCardData');
        safetyHubBrowserProxy.setSafeBrowsingCardData(enhancedCardData);
        await changeSafeBrowsingGeneratedPref(SafeBrowsingSetting.ENHANCED);
        assertSafeBrowsingCard(enhancedCardData);

        // Change the generated.safe_browsing pref to DISABLED and check that it
        // triggers getSafeBrowsingCardData call and UI change.
        safetyHubBrowserProxy.resetResolver('getSafeBrowsingCardData');
        safetyHubBrowserProxy.setSafeBrowsingCardData(disabledCardData);
        await changeSafeBrowsingGeneratedPref(SafeBrowsingSetting.DISABLED);
        assertSafeBrowsingCard(disabledCardData);

        // Change the generated.safe_browsing pref to STANDARD and check that it
        // triggers getSafeBrowsingCardData call and UI change.
        safetyHubBrowserProxy.resetResolver('getSafeBrowsingCardData');
        safetyHubBrowserProxy.setSafeBrowsingCardData(standardCardData);
        await changeSafeBrowsingGeneratedPref(SafeBrowsingSetting.STANDARD);
        assertSafeBrowsingCard(standardCardData);
      });

  test('Dismiss all menu notifications on page load', async function() {
    Router.getInstance().navigateTo(routes.SAFETY_HUB);
    await safetyHubBrowserProxy.whenCalled('dismissActiveMenuNotification');
  });

  test('Metric Recording for Dashboard State', async function() {
    const safeCardData: CardInfo = {
      header: 'Dummy header',
      subheader: 'Dummy subheader',
      state: CardState.SAFE,
    };

    const unsafeCardData: CardInfo = {
      header: 'Dummy header',
      subheader: 'Dummy subheader',
      state: CardState.WARNING,
    };

    function reset() {
      // Reset all cards and modules on safety hub page.
      safetyHubBrowserProxy.setPasswordCardData(safeCardData);
      safetyHubBrowserProxy.setVersionCardData(safeCardData);
      safetyHubBrowserProxy.setSafeBrowsingCardData(safeCardData);
      safetyHubBrowserProxy.setUnusedSitePermissions([]);
      safetyHubBrowserProxy.setNotificationPermissionReview([]);
      safetyHubBrowserProxy.setNumberOfExtensionsThatNeedReview(0);
      metricsBrowserProxy.reset();
    }

    async function refresh(): Promise<void> {
      // Refresh the page to consume recent mock data.
      document.body.removeChild(testElement);
      testElement = document.createElement('settings-safety-hub-page');
      document.body.appendChild(testElement);
      await flushTasks();
    }

    reset();
    await refresh();
    // Expect recordSafetyHubDashboardAnyWarning is called as false since
    // there is no warning.
    let result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubDashboardAnyWarning');
    assertEquals(false, result);

    // Check general interaction and impression metrics.
    result = await metricsBrowserProxy.whenCalled('recordSafetyHubImpression');
    assertEquals(SafetyHubSurfaces.SAFETY_HUB_PAGE, result);
    result = await metricsBrowserProxy.whenCalled('recordSafetyHubInteraction');
    assertEquals(SafetyHubSurfaces.SAFETY_HUB_PAGE, result);

    // Expect recordSafetyHubModuleWarningImpression is called for password
    // card.
    reset();
    safetyHubBrowserProxy.setPasswordCardData(unsafeCardData);
    await refresh();
    result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubModuleWarningImpression');
    assertEquals(SafetyHubModuleType.PASSWORDS, result);

    // Expect recordSafetyHubModuleWarningImpression is called for version card.
    reset();
    safetyHubBrowserProxy.setVersionCardData(unsafeCardData);
    await refresh();
    result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubModuleWarningImpression');
    assertEquals(SafetyHubModuleType.VERSION, result);

    // Expect recordSafetyHubModuleWarningImpression is called for safe browsing
    // card.
    reset();
    safetyHubBrowserProxy.setSafeBrowsingCardData(unsafeCardData);
    await refresh();
    result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubModuleWarningImpression');
    assertEquals(SafetyHubModuleType.SAFE_BROWSING, result);

    // Expect recordSafetyHubModuleWarningImpression is called for unused site
    // permissions.
    reset();
    safetyHubBrowserProxy.setUnusedSitePermissions(
        unusedSitePermissionMockData);
    await refresh();
    result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubModuleWarningImpression');
    assertEquals(SafetyHubModuleType.PERMISSIONS, result);

    // Expect recordSafetyHubModuleWarningImpression is called for notification
    // permissions.
    reset();
    safetyHubBrowserProxy.setNotificationPermissionReview(
        notificationPermissionMockData);
    await refresh();
    result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubModuleWarningImpression');
    assertEquals(SafetyHubModuleType.NOTIFICATIONS, result);

    // Expect recordSafetyHubModuleWarningImpression is called for extensions.
    reset();
    safetyHubBrowserProxy.setNumberOfExtensionsThatNeedReview(1);
    await refresh();
    result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubModuleWarningImpression');
    assertEquals(SafetyHubModuleType.EXTENSIONS, result);

    // Expect recordSafetyHubDashboardAnyWarning is called as true.
    result = await metricsBrowserProxy.whenCalled(
        'recordSafetyHubDashboardAnyWarning');
    assertEquals(true, result);
  });

  test('Metric Recording for Education module', async function() {
    assertNoRecommendationState(true);

    const eduModule = testElement.shadowRoot!.querySelector<HTMLElement>(
        '#userEducationModule');
    const links =
        eduModule!.shadowRoot!.querySelectorAll<HTMLAnchorElement>('a');
    assertEquals(3, links.length);

    // Check clicking the Safety Tools link causes metric recording.
    assertTrue(!!links[0]);
    links[0].click();
    await safetyHubBrowserProxy.whenCalled('recordSafetyHubInteraction');
    assertEquals(
        'Settings.SafetyHub.SafetyToolsLinkClicked',
        await metricsBrowserProxy.whenCalled('recordAction'));
    metricsBrowserProxy.reset();

    // Check clicking the Incognito link causes metric recording.
    assertTrue(!!links[1]);
    links[1].click();
    await safetyHubBrowserProxy.whenCalled('recordSafetyHubInteraction');
    assertEquals(
        'Settings.SafetyHub.IncognitoLinkClicked',
        await metricsBrowserProxy.whenCalled('recordAction'));
    metricsBrowserProxy.reset();

    // Check clicking the Safe Browsing link causes metric recording.
    assertTrue(!!links[2]);
    links[2].click();
    await safetyHubBrowserProxy.whenCalled('recordSafetyHubInteraction');
    assertEquals(
        'Settings.SafetyHub.SafeBrowsingLinkClicked',
        await metricsBrowserProxy.whenCalled('recordAction'));
  });

  test('Record Safety Hub page visit', async function() {
    // Override setTimeout, and only alter behavior for the 20s timeout (the
    // delay for considering a SH page visit).
    // Using MockTimer did not work here, as it interfered with many other,
    // unrelated timers.
    const origSetTimeout = window.setTimeout;
    window.setTimeout = function(
        handler: TimerHandler, timeout: number|undefined): number {
      if (timeout === 20000) {
        const callback = handler as Function;
        callback();
        return 0;
      }
      return origSetTimeout(handler, timeout);
    };

    document.body.removeChild(testElement);
    testElement = document.createElement('settings-safety-hub-page');
    document.body.appendChild(testElement);
    await flushTasks();

    await safetyHubBrowserProxy.whenCalled('recordSafetyHubPageVisit');

    window.setTimeout = origSetTimeout;
  });
});