chromium/chrome/test/data/webui/chromeos/settings/internet_page/internet_page_test.ts

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

import 'chrome://os-settings/os_settings.js';
import 'chrome://os-settings/lazy_load.js';

import {ApnSubpageElement, CrActionMenuElement, CrExpandButtonElement, CrIconButtonElement, CrToggleElement, EsimRemoveProfileDialogElement, EsimRenameDialogElement, NetworkSummaryElement, NetworkSummaryItemElement, OsSettingsCellularSetupDialogElement, OsSettingsSubpageElement, PaperTooltipElement, Router, routes, settingMojom, SettingsInternetPageElement} from 'chrome://os-settings/os_settings.js';
import {CellularSetupPageName} from 'chrome://resources/ash/common/cellular_setup/cellular_types.js';
import {setESimManagerRemoteForTesting} from 'chrome://resources/ash/common/cellular_setup/mojo_interface_provider.js';
import {MojoConnectivityProvider} from 'chrome://resources/ash/common/connectivity/mojo_connectivity_provider.js';
import {setHotspotConfigForTesting} from 'chrome://resources/ash/common/hotspot/cros_hotspot_config.js';
import {HotspotAllowStatus, HotspotInfo, HotspotState, WiFiBand, WiFiSecurityMode} from 'chrome://resources/ash/common/hotspot/cros_hotspot_config.mojom-webui.js';
import {FakeHotspotConfig} from 'chrome://resources/ash/common/hotspot/fake_hotspot_config.js';
import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';

import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {ApnProperties, DeviceStateProperties, GlobalPolicy, InhibitReason, MAX_NUM_CUSTOM_APNS, VpnType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
import {ConnectionStateType, DeviceStateType, NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
import {assertEquals, assertFalse, assertNotEquals, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {FakeNetworkConfig} from 'chrome://webui-test/chromeos/fake_network_config_mojom.js';
import {FakePasspointService} from 'chrome://webui-test/chromeos/fake_passpoint_service_mojom.js';
import {FakeESimManagerRemote} from 'chrome://webui-test/cr_components/chromeos/cellular_setup/fake_esim_manager_remote.js';
import {flushTasks, waitAfterNextRender, waitBeforeNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';

import {clearBody} from '../utils.js';

suite('<settings-internet-page>', () => {
  let internetPage: SettingsInternetPageElement;
  let mojoApi: FakeNetworkConfig;
  let eSimManagerRemote: FakeESimManagerRemote;
  let passpointService: FakePasspointService;
  let hotspotConfig: FakeHotspotConfig;

  const fakePrefs = {
    vpn_config_allowed: {
      key: 'vpn_config_allowed',
      type: chrome.settingsPrivate.PrefType.BOOLEAN,
      value: true,
    },
    arc: {
      vpn: {
        always_on: {
          vpn_package: {
            key: 'vpn_package',
            type: chrome.settingsPrivate.PrefType.STRING,
            value: '',
          },
        },
      },
    },
  };

  function setNetworksForTest(networks: OncMojo.NetworkStateProperties[]):
      void {
    mojoApi.resetForTest();
    mojoApi.addNetworksForTest(networks);
  }

  function setDoesDisconnectProhibitedAlwaysOnVpnPrefs(
      arcVpnAlwaysOnPackageNamePrefValue: string,
      vpnConfigAllowedPrefValue: boolean): void {
    fakePrefs.arc.vpn.always_on.vpn_package.value =
        arcVpnAlwaysOnPackageNamePrefValue;
    fakePrefs.vpn_config_allowed.value = vpnConfigAllowedPrefValue;
    internetPage.prefs = {...fakePrefs};
  }

  async function navigateToCellularSetupDialog(
      showPSimFlow: boolean, isCellularEnabled: boolean): Promise<void> {
    const params = new URLSearchParams();
    params.append('guid', 'cellular_guid');
    params.append('type', 'Cellular');
    params.append('name', 'cellular');
    params.append('showCellularSetup', 'true');
    if (showPSimFlow) {
      params.append('showPsimFlow', 'true');
    }

    // Pretend that we initially started on the INTERNET_NETWORKS route with the
    // params.
    Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
    internetPage.currentRouteChanged(routes.INTERNET_NETWORKS, undefined);

    // Update the device state here to trigger an
    // attemptShowCellularSetupDialog_() call.
    mojoApi.setNetworkTypeEnabledState(
        NetworkType.kCellular, isCellularEnabled);

    await flushTasks();
  }

  async function assertWarningMessageVisibility(warningMessage: HTMLElement) {
    assertTrue(!!warningMessage);
    // Warning message should be initially hidden.
    assertTrue(warningMessage.hidden);

    // Add a pSIM network.
    mojoApi.setNetworkTypeEnabledState(NetworkType.kCellular, true);
    const pSimNetwork = OncMojo.getDefaultManagedProperties(
        NetworkType.kCellular, 'cellular1', 'foo');
    pSimNetwork.connectionState = ConnectionStateType.kConnected;
    mojoApi.setManagedPropertiesForTest(pSimNetwork);
    await flushTasks();

    // Warning message should now be showing.
    assertFalse(warningMessage.hidden);

    // Disconnect from the pSIM network.
    pSimNetwork.connectionState = ConnectionStateType.kNotConnected;
    mojoApi.setManagedPropertiesForTest(pSimNetwork);
    await flushTasks();
    // Warning message should be hidden.
    assertTrue(warningMessage.hidden);

    // Add an eSIM network.
    const eSimNetwork = OncMojo.getDefaultManagedProperties(
        NetworkType.kCellular, 'cellular2', 'foo');
    eSimNetwork.connectionState = ConnectionStateType.kConnected;
    eSimNetwork.typeProperties.cellular!.eid = 'eid';
    mojoApi.setManagedPropertiesForTest(eSimNetwork);
    await flushTasks();

    // Warning message should be showing again.
    assertFalse(warningMessage.hidden);
  }

  async function navigateToCellularDetailPage(): Promise<void> {
    await init();

    const cellularNetwork = OncMojo.getDefaultManagedProperties(
        NetworkType.kCellular, 'cellular1', 'name1');
    cellularNetwork.typeProperties.cellular!.eid = 'eid';
    mojoApi.setManagedPropertiesForTest(cellularNetwork);

    const params = new URLSearchParams();
    params.append('guid', cellularNetwork.guid);
    Router.getInstance().navigateTo(routes.NETWORK_DETAIL, params);
    await flushTasks();
  }

  async function navigateToApnSubpage(): Promise<void> {
    await navigateToCellularDetailPage();
    internetPage.shadowRoot!.querySelector('settings-internet-detail-subpage')!
        .shadowRoot!.querySelector<HTMLElement>('#apnSubpageButton')!.click();
    await flushTasks();
  }

  function getNetworkSummaryElement(): NetworkSummaryElement {
    const networkSummary =
        internetPage.shadowRoot!.querySelector('network-summary');
    assertTrue(!!networkSummary);
    return networkSummary;
  }

  function queryCellularSetupDialog(): OsSettingsCellularSetupDialogElement|
      null {
    return internetPage.shadowRoot!.querySelector(
        'os-settings-cellular-setup-dialog');
  }

  function queryCreateCustomApnButton(): HTMLButtonElement|null {
    return internetPage.shadowRoot!.querySelector<HTMLButtonElement>(
        '#createCustomApnButton');
  }

  function queryDiscoverMoreApnsButton(): HTMLButtonElement|null {
    return internetPage.shadowRoot!.querySelector<HTMLButtonElement>(
        '#discoverMoreApnsButton');
  }

  function queryApnActionMenuButton(): CrIconButtonElement|null {
    return internetPage.shadowRoot!.querySelector<CrIconButtonElement>(
        '#apnActionMenuButton');
  }

  function queryApnDotsMenu(): CrActionMenuElement|null {
    return internetPage.shadowRoot!.querySelector<CrActionMenuElement>(
        '#apnDotsMenu');
  }

  async function init(): Promise<void> {
    mojoApi.resetForTest();

    clearBody();
    internetPage = document.createElement('settings-internet-page');
    document.body.appendChild(internetPage);

    setDoesDisconnectProhibitedAlwaysOnVpnPrefs(
        /*arcVpnAlwaysOnPackageNamePrefValue=*/ '',
        /*vpnConfigAllowedPrefValue=*/ false);
    await flushTasks();
    await Promise.all([
      mojoApi.whenCalled('getNetworkStateList'),
      mojoApi.whenCalled('getDeviceStateList'),
    ]);
  }

  setup(() => {
    loadTimeData.overrideValues({
      bypassConnectivityCheck: false,
      internetAddConnection: 'internetAddConnection',
      internetAddConnectionExpandA11yLabel:
          'internetAddConnectionExpandA11yLabel',
      internetAddConnectionNotAllowed: 'internetAddConnectionNotAllowed',
      internetAddThirdPartyVPN: 'internetAddThirdPartyVPN',
      internetAddVPN: 'internetAddVPN',
      internetAddWiFi: 'internetAddWiFi',
      internetDetailPageTitle: 'internetDetailPageTitle',
      internetKnownNetworksPageTitle: 'internetKnownNetworksPageTitle',
    });

    mojoApi = new FakeNetworkConfig();
    MojoInterfaceProviderImpl.getInstance().setMojoServiceRemoteForTest(
        mojoApi);
    eSimManagerRemote = new FakeESimManagerRemote();
    setESimManagerRemoteForTesting(eSimManagerRemote);
    passpointService = new FakePasspointService();
    MojoConnectivityProvider.getInstance().setPasspointServiceForTest(
        passpointService);
    hotspotConfig = new FakeHotspotConfig();
    setHotspotConfigForTesting(hotspotConfig);
  });

  teardown(() => {
    Router.getInstance().resetRouteForTesting();
  });

  suite('MainPage', () => {
    test('Ethernet', async () => {
      await init();
      const networkSummary = getNetworkSummaryElement();

      // Default fake device state is Ethernet enabled only.
      const ethernet =
          networkSummary.shadowRoot!.querySelector<NetworkSummaryItemElement>(
              '#Ethernet');
      assertTrue(!!ethernet);
      assertEquals(1, ethernet.networkStateList.length);

      assertNull(networkSummary.shadowRoot!.querySelector('#Cellular'));
      assertNull(networkSummary.shadowRoot!.querySelector('#VPN'));
      assertNull(networkSummary.shadowRoot!.querySelector('#WiFi'));
    });

    test('WiFi', async () => {
      await init();
      setNetworksForTest([
        OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi1'),
        OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi2'),
      ]);
      mojoApi.setNetworkTypeEnabledState(NetworkType.kWiFi, true);
      await flushTasks();

      const wifi =
          getNetworkSummaryElement()
              .shadowRoot!.querySelector<NetworkSummaryItemElement>('#WiFi');
      assertTrue(!!wifi);
      assertEquals(2, wifi.networkStateList.length);
    });

    test('WiFiToggle', async () => {
      await init();
      // Make WiFi an available but disabled technology.
      mojoApi.setNetworkTypeEnabledState(NetworkType.kWiFi, false);
      await flushTasks();

      const networkSummary = getNetworkSummaryElement();
      const wifi = networkSummary.shadowRoot!.querySelector('#WiFi');
      assertTrue(!!wifi);

      // Ensure that the initial state is disabled and the toggle is
      // enabled but unchecked.
      let wifiDevice = mojoApi.getDeviceStateForTest(NetworkType.kWiFi);
      assertTrue(!!wifiDevice);
      assertEquals(DeviceStateType.kDisabled, wifiDevice.deviceState);
      const toggle = wifi.shadowRoot!.querySelector<CrToggleElement>(
          '#deviceEnabledButton');
      assertTrue(!!toggle);
      assertFalse(toggle.disabled);
      assertFalse(toggle.checked);

      // Tap the enable toggle button and ensure the state becomes enabling.
      toggle.click();
      await flushTasks();

      assertTrue(toggle.checked);
      wifiDevice = mojoApi.getDeviceStateForTest(NetworkType.kWiFi);
      assertTrue(!!wifiDevice);
      assertEquals(DeviceStateType.kEnabling, wifiDevice.deviceState);
    });

    test('Deep link to WiFi toggle', async () => {
      await init();
      // Make WiFi an available but disabled technology.
      mojoApi.setNetworkTypeEnabledState(NetworkType.kWiFi, false);

      const settingId = settingMojom.Setting.kWifiOnOff;
      const params = new URLSearchParams();
      params.append('settingId', settingId.toString());
      Router.getInstance().navigateTo(routes.INTERNET, params);
      await flushTasks();

      const deepLinkElement =
          getNetworkSummaryElement()
              .shadowRoot!.querySelector('#WiFi')!.shadowRoot!
              .querySelector<HTMLElement>('#deviceEnabledButton');
      assertTrue(!!deepLinkElement);
      await waitAfterNextRender(deepLinkElement);
      assertEquals(
          deepLinkElement, getDeepActiveElement(),
          `Toggle WiFi should be focused for settingId=${settingId}.`);
    });

    test('Deep link to APN menu button', async () => {
      await init();

      const cellularNetwork = OncMojo.getDefaultManagedProperties(
          NetworkType.kCellular, 'cellular1', 'name1');
      cellularNetwork.typeProperties.cellular!.eid = 'eid';
      mojoApi.setManagedPropertiesForTest(cellularNetwork);

      const settingId = settingMojom.Setting.kCellularAddApn;
      const params = new URLSearchParams();
      params.append('settingId', settingId.toString());
      params.append('guid', cellularNetwork.guid);
      Router.getInstance().navigateTo(routes.APN, params);
      await flushTasks();

      const deepLinkElement = queryApnActionMenuButton();
      assertTrue(!!deepLinkElement);
      await waitAfterNextRender(deepLinkElement);
      assertEquals(
          deepLinkElement, getDeepActiveElement(),
          `APN menu button be focused for settingId=${settingId}.`);
    });

    suite('VPN', () => {
      test('VpnProviders', async () => {
        await init();
        mojoApi.setVpnProvidersForTest([
          {
            type: VpnType.kExtension,
            providerId: 'extension_id1',
            providerName: 'MyExtensionVPN1',
            appId: 'extension_id1',
            lastLaunchTime: {internalValue: BigInt(0)},
          },
          {
            type: VpnType.kArc,
            providerId: 'vpn.app.package1',
            providerName: 'MyArcVPN1',
            appId: 'arcid1',
            lastLaunchTime: {internalValue: BigInt(1)},
          },
          {
            type: VpnType.kArc,
            providerId: 'vpn.app.package2',
            providerName: 'MyArcVPN2',
            appId: 'arcid2',
            lastLaunchTime: {internalValue: BigInt(2)},
          },
        ]);
        await flushTasks();

        const vpnProviders = internetPage.get('vpnProviders_');
        assertEquals(3, vpnProviders.length);
        // Ensure providers are sorted by type and lastLaunchTime.
        assertEquals('extension_id1', vpnProviders[0].providerId);
        assertEquals('vpn.app.package2', vpnProviders[1].providerId);
        assertEquals('vpn.app.package1', vpnProviders[2].providerId);
      });

      function expandAddConnections(): void {
        const button =
            internetPage.shadowRoot!.querySelector<CrExpandButtonElement>(
                '#expandAddConnections');
        assertTrue(!!button);
        button.expanded = true;
      }

      function addVpnNetworkAndSetDeviceState(vpnProhibited: boolean): void {
        setNetworksForTest([
          OncMojo.getDefaultNetworkState(NetworkType.kVPN, 'vpn'),
        ]);
        mojoApi.setDeviceStateForTest({
          type: NetworkType.kVPN,
          deviceState: vpnProhibited ? DeviceStateType.kProhibited :
                                       DeviceStateType.kEnabled,
        } as DeviceStateProperties);
      }

      function queryAddVpnLabel() {
        return internetPage.shadowRoot!.querySelector('#add-vpn-label');
      }

      function queryAddVpnButton() {
        return internetPage.shadowRoot!.querySelector<CrIconButtonElement>(
            '#add-vpn-button');
      }

      test(
          'should show disabled add VPN button when allow only policy ' +
              'WiFi networks to connect is enabled and VPN is prohibited',
          async () => {
            await init();
            mojoApi.setGlobalPolicy({
              allowOnlyPolicyCellularNetworks: true,
            } as GlobalPolicy);
            expandAddConnections();
            addVpnNetworkAndSetDeviceState(/* vpnProhibited= */ true);
            await flushTasks();

            assertTrue(isVisible(queryAddVpnLabel()));
            assertTrue(queryAddVpnButton()!.disabled);
          });

      test(
          'should show enabled add VPN button when allow only policy ' +
              'WiFi networks to connect is enabled and VPN is allowed',
          async () => {
            await init();
            internetPage.set('globalPolicy_', {
              allowOnlyPolicyWifiNetworksToConnect: true,
            });
            expandAddConnections();
            addVpnNetworkAndSetDeviceState(/* vpnProhibited= */ false);
            await flushTasks();

            assertTrue(isVisible(queryAddVpnLabel()));
            assertFalse(queryAddVpnButton()!.disabled);
          });

      [{
        vpnProhibited: true,
        arcVpnAlwaysOnPackageNamePrefValue: '',
        manualDisconnectionAllowed: false,
      },
       {
         vpnProhibited: true,
         arcVpnAlwaysOnPackageNamePrefValue: '',
         manualDisconnectionAllowed: true,
       },
       {
         vpnProhibited: true,
         arcVpnAlwaysOnPackageNamePrefValue: 'PackageName',
         manualDisconnectionAllowed: false,
       },
       {
         vpnProhibited: true,
         arcVpnAlwaysOnPackageNamePrefValue: 'PackageName',
         manualDisconnectionAllowed: true,
       },
       {
         vpnProhibited: false,
         arcVpnAlwaysOnPackageNamePrefValue: '',
         manualDisconnectionAllowed: false,
       },
       {
         vpnProhibited: false,
         arcVpnAlwaysOnPackageNamePrefValue: '',
         manualDisconnectionAllowed: true,
       },
       {
         vpnProhibited: false,
         arcVpnAlwaysOnPackageNamePrefValue: 'PackageName',
         manualDisconnectionAllowed: false,
       },
       {
         vpnProhibited: false,
         arcVpnAlwaysOnPackageNamePrefValue: 'PackageName',
         manualDisconnectionAllowed: true,
       },
      ].forEach(({
                  vpnProhibited,
                  arcVpnAlwaysOnPackageNamePrefValue,
                  manualDisconnectionAllowed,
                }) => {
        test(
            `VPNs prohibited by policy: ${
                vpnProhibited}, always on VPN set by policy: ${
                  !!arcVpnAlwaysOnPackageNamePrefValue}, ` +
                `setDoesDisconnectProhibitedAlwaysOnVpnPrefs allowed ` +
                `by policy: ${manualDisconnectionAllowed}`,
            async () => {
          await init();

          expandAddConnections();
          addVpnNetworkAndSetDeviceState(vpnProhibited);
          setDoesDisconnectProhibitedAlwaysOnVpnPrefs(
              /* arcVpnAlwaysOnPackageNamePrefValue= */
              arcVpnAlwaysOnPackageNamePrefValue,
              /* vpnConfigAllowedPrefValue= */ manualDisconnectionAllowed);
          await flushTasks();

          const vpnPolicyIndicator =
              internetPage.shadowRoot!.querySelector('#vpnPolicyIndicator');

          if (vpnProhibited ||
              (!!arcVpnAlwaysOnPackageNamePrefValue &&
               !manualDisconnectionAllowed)) {
            assertTrue(isVisible(vpnPolicyIndicator));
          } else {
            assertFalse(isVisible(vpnPolicyIndicator));
          }

          const policyIndicator =
              getNetworkSummaryElement()
                  .shadowRoot!.querySelector('#VPN')!.shadowRoot!.querySelector(
                      '#policyIndicator');
          if (vpnProhibited) {
            assertTrue(isVisible(policyIndicator));
          } else {
            // Note: Users are still allowed to configure existing VPNs
            // they set previously if |arcVpnAlwaysOnPackageNamePrefValue|
            // is not set and |manualDisconnectionAllowed| is false.
            // TODO(http://b/302390893): Consider renaming
            // |vpn_config_allowed| pref to something like
            // "manual_disconnection_allowed", or discuss with DPanel team
            // about rewording
            //  https://screenshot.googleplex.com/4KLUdLUtPsvNMDT
            assertFalse(isVisible(policyIndicator));
          }
            });
      });
    });

    test('Deep link to mobile on/off toggle', async () => {
      await init();
      // Make WiFi an available but disabled technology.
      mojoApi.setNetworkTypeEnabledState(NetworkType.kCellular, false);

      const setting = settingMojom.Setting.kMobileOnOff;
      const params = new URLSearchParams();
      params.append('settingId', setting.toString());
      Router.getInstance().navigateTo(routes.INTERNET, params);
      await flushTasks();

      const deepLinkElement =
          getNetworkSummaryElement()
              .shadowRoot!.querySelector('#Cellular')!.shadowRoot!
              .querySelector<HTMLElement>('#deviceEnabledButton');
      assertTrue(!!deepLinkElement);
      await waitAfterNextRender(deepLinkElement);
      assertEquals(
          deepLinkElement, getDeepActiveElement(),
          `Toggle mobile on/off should be focused for settingId=${setting}.`);
    });

    test('Show rename esim profile dialog', async () => {
      await init();
      eSimManagerRemote.addEuiccForTest(1);
      await flushTasks();

      function queryEsimRenameDialog(): EsimRenameDialogElement|null {
        return internetPage.shadowRoot!.querySelector('esim-rename-dialog');
      }

      let renameDialog = queryEsimRenameDialog();
      assertNull(renameDialog);

      const event = new CustomEvent(
          'show-esim-profile-rename-dialog', {detail: {iccid: '1'}});
      internetPage.dispatchEvent(event);
      await flushTasks();

      renameDialog = queryEsimRenameDialog();
      assertTrue(!!renameDialog);
      await assertWarningMessageVisibility(renameDialog.$.warningMessage);
    });

    test('Show remove esim profile dialog', async () => {
      await init();
      eSimManagerRemote.addEuiccForTest(1);
      await flushTasks();

      function queryEsimRemoveProfileDialog(): EsimRemoveProfileDialogElement|
          null {
        return internetPage.shadowRoot!.querySelector(
            'esim-remove-profile-dialog');
      }

      let removeDialog = queryEsimRemoveProfileDialog();
      assertNull(removeDialog);

      const event = new CustomEvent(
          'show-esim-remove-profile-dialog', {detail: {iccid: '1'}});
      internetPage.dispatchEvent(event);
      await flushTasks();

      removeDialog = queryEsimRemoveProfileDialog();
      assertTrue(!!removeDialog);
      await assertWarningMessageVisibility(removeDialog.$.warningMessage);
    });
  });

  test(
      'Show pSIM flow cellular setup dialog if route params' +
          'contain showCellularSetup and showPsimFlow',
      async () => {
        await init();

        assertNull(queryCellularSetupDialog());

        await navigateToCellularSetupDialog(
            /*showPSimFlow=*/ true, /*isCellularEnabled=*/ true);

        const cellularSetupDialog = queryCellularSetupDialog();
        assertTrue(!!cellularSetupDialog);
        const psimFlow =
            cellularSetupDialog.shadowRoot!.querySelector('cellular-setup')!
                .shadowRoot!.querySelector('#psim-flow-ui');
        assertTrue(!!psimFlow);
      });

  test(
      'Show eSIM flow cellular setup dialog if route params' +
          'contains showCellularSetup, does not contain showPsimFlow,' +
          'connected to a non-cellular network, and cellular enabled',
      async () => {
        await init();
        eSimManagerRemote.addEuiccForTest(1);

        const wifiNetwork =
            OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi');
        wifiNetwork.connectionState = ConnectionStateType.kOnline;
        mojoApi.addNetworksForTest([wifiNetwork]);
        await flushTasks();

        assertNull(queryCellularSetupDialog());

        await navigateToCellularSetupDialog(
            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ true);

        const cellularSetupDialog = queryCellularSetupDialog();
        assertTrue(!!cellularSetupDialog);
        const esimFlow =
            cellularSetupDialog.shadowRoot!.querySelector('cellular-setup')!
                .shadowRoot!.querySelector('#esim-flow-ui');
        assertTrue(!!esimFlow);
      });

  test(
      'Show no connection toast if route params' +
          'contain showCellularSetup, does not contain showPsimFlow,' +
          'cellular is enabled, but not connected to a non-cellular network',
      async () => {
        await init();
        eSimManagerRemote.addEuiccForTest(1);

        assertNull(queryCellularSetupDialog());

        await navigateToCellularSetupDialog(
            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ true);

        assertTrue(internetPage.$.errorToast.open);
        assertEquals(
            internetPage.i18n('eSimNoConnectionErrorToast'),
            internetPage.$.errorToastMessage.innerHTML);
        assertNull(queryCellularSetupDialog());
      });

  test(
      'Show eSIM flow cellular setup dialog if route params ' +
          'contain showCellularSetup, does not contain showPsimFlow, ' +
          'cellular is enabled, not connected to a non-cellular network ' +
          'but cellular bypass esim installation connectivity flag is enabled',
      async () => {
        loadTimeData.overrideValues({
          bypassConnectivityCheck: true,
        });
        await init();
        eSimManagerRemote.addEuiccForTest(1);

        assertNull(queryCellularSetupDialog());

        await navigateToCellularSetupDialog(
            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ true);

        const cellularSetupDialog = queryCellularSetupDialog();
        assertTrue(!!cellularSetupDialog);
        const esimFlow =
            cellularSetupDialog.shadowRoot!.querySelector('cellular-setup')!
                .shadowRoot!.querySelector('#esim-flow-ui');
        assertTrue(!!esimFlow);
      });

  test(
      'Show mobile data not enabled toast if route params' +
          'contains showCellularSetup, does not contain showPsimFlow,' +
          'connected to a non-cellular network, but cellular not enabled',
      async () => {
        await init();
        eSimManagerRemote.addEuiccForTest(1);

        const wifiNetwork =
            OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi');
        wifiNetwork.connectionState = ConnectionStateType.kOnline;
        mojoApi.addNetworksForTest([wifiNetwork]);
        await flushTasks();

        assertNull(queryCellularSetupDialog());

        await navigateToCellularSetupDialog(
            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ false);

        assertTrue(internetPage.$.errorToast.open);
        assertEquals(
            internetPage.i18n('eSimMobileDataNotEnabledErrorToast'),
            internetPage.$.errorToastMessage.innerHTML);
        assertNull(queryCellularSetupDialog());
      });

  test(
      'Show profile limit reached toast if route params' +
          'contains showCellularSetup, does not contain showPsimFlow,' +
          'connected to a non-cellular network, cellular enabled,' +
          'but profile limit is reached',
      async () => {
        await init();
        eSimManagerRemote.addEuiccForTest(/*numProfiles=*/ 5);

        const wifiNetwork =
            OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi');
        wifiNetwork.connectionState = ConnectionStateType.kOnline;
        mojoApi.addNetworksForTest([wifiNetwork]);
        await flushTasks();

        assertNull(queryCellularSetupDialog());

        await navigateToCellularSetupDialog(
            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ true);

        assertTrue(internetPage.$.errorToast.open);
        assertEquals(
            internetPage.i18n('eSimProfileLimitReachedErrorToast', 5),
            internetPage.$.errorToastMessage.innerHTML);
        assertNull(queryCellularSetupDialog());
      });

  test('Show sim lock dialog through URL parameters', async () => {
    await init();

    const params = new URLSearchParams();
    params.append('type', OncMojo.getNetworkTypeString(NetworkType.kCellular));
    params.append('showSimLockDialog', 'true');

    // Pretend that we initially started on the INTERNET_NETWORKS route with the
    // params.
    Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
    internetPage.currentRouteChanged(routes.INTERNET_NETWORKS, undefined);

    // Update the device state here to trigger an onDeviceStatesChanged_() call.
    mojoApi.setDeviceStateForTest({
      type: NetworkType.kCellular,
      deviceState: DeviceStateType.kEnabled,
      inhibitReason: InhibitReason.kNotInhibited,
      simLockStatus: {
        lockEnabled: true,
      },
    } as DeviceStateProperties);
    await flushTasks();

    const simLockDialogs =
        internetPage.shadowRoot!.querySelector('sim-lock-dialogs');
    assertTrue(!!simLockDialogs);
    assertTrue(simLockDialogs.isDialogOpen);
  });

  test('Show carrier lock sub header when locked', async () => {
    await init();

    const params = new URLSearchParams();
    params.append('type', OncMojo.getNetworkTypeString(NetworkType.kCellular));

    // Pretend that we initially started on the INTERNET_NETWORKS route with the
    // params.
    Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
    internetPage.currentRouteChanged(routes.INTERNET_NETWORKS, undefined);

    // Update the device state here to trigger an onDeviceStatesChanged_() call.
    mojoApi.setDeviceStateForTest({
      type: NetworkType.kCellular,
      deviceState: DeviceStateType.kEnabled,
      inhibitReason: InhibitReason.kNotInhibited,
      isCarrierLocked: true,
    } as DeviceStateProperties);
    await flushTasks();

    const cellularSubtitle =
        internetPage.shadowRoot!.querySelector<HTMLElement>(
            '#cellularSubtitle');
    assertTrue(!!cellularSubtitle);
  });

  test(
      'Verify carrier lock sub header not displayed when unlocked',
      async () => {
        await init();

        const params = new URLSearchParams();
        params.append(
            'type', OncMojo.getNetworkTypeString(NetworkType.kCellular));

        // Pretend that we initially started on the INTERNET_NETWORKS route with
        // the params.
        Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
        internetPage.currentRouteChanged(routes.INTERNET_NETWORKS, undefined);

        // Update the device state here to trigger an onDeviceStatesChanged_()
        // call.
        mojoApi.setDeviceStateForTest({
          type: NetworkType.kCellular,
          deviceState: DeviceStateType.kEnabled,
          inhibitReason: InhibitReason.kNotInhibited,
          isCarrierLocked: false,
        } as DeviceStateProperties);
        await flushTasks();

        const cellularSubtitle =
            internetPage.shadowRoot!.querySelector<HTMLElement>(
                '#cellularSubtitle');
        assertNull(cellularSubtitle);
      });

  test('Show modem flashing sub header when flashing', async () => {
    await init();

    const params = new URLSearchParams();
    params.append('type', OncMojo.getNetworkTypeString(NetworkType.kCellular));

    // Pretend that we initially started on the INTERNET_NETWORKS route with the
    // params.
    Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
    internetPage.currentRouteChanged(routes.INTERNET_NETWORKS, undefined);

    // Update the device state here to trigger an onDeviceStatesChanged_() call.
    mojoApi.setDeviceStateForTest({
      type: NetworkType.kCellular,
      deviceState: DeviceStateType.kEnabled,
      inhibitReason: InhibitReason.kNotInhibited,
      isFlashing: true,
    } as DeviceStateProperties);
    await flushTasks();

    const flashingSubtitle =
        internetPage.shadowRoot!.querySelector<HTMLElement>(
            '#flashingSubtitle');
    assertTrue(!!flashingSubtitle);
  });

  test('Not showing modem flashing sub header when flashing', async () => {
    await init();

    const params = new URLSearchParams();
    params.append('type', OncMojo.getNetworkTypeString(NetworkType.kCellular));

    // Pretend that we initially started on the INTERNET_NETWORKS route with the
    // params.
    Router.getInstance().navigateTo(routes.INTERNET_NETWORKS, params);
    internetPage.currentRouteChanged(routes.INTERNET_NETWORKS, undefined);

    // Update the device state here to trigger an onDeviceStatesChanged_() call.
    mojoApi.setDeviceStateForTest({
      type: NetworkType.kCellular,
      deviceState: DeviceStateType.kEnabled,
      inhibitReason: InhibitReason.kNotInhibited,
      isFlashing: false,
    } as DeviceStateProperties);
    await flushTasks();

    const flashingSubtitle =
        internetPage.shadowRoot!.querySelector<HTMLElement>(
            '#flashingSubtitle');
    assertNull(flashingSubtitle);
  });

  test(
      'Show no connection toast if receive show-cellular-setup' +
          'event and not connected to non-cellular network',
      async () => {
        await init();
        eSimManagerRemote.addEuiccForTest(/*numProfiles=*/ 1);
        mojoApi.setNetworkTypeEnabledState(NetworkType.kCellular, true);
        await flushTasks();

        assertFalse(internetPage.$.errorToast.open);

        // Send event, toast should show, dialog hidden.
        const event = new CustomEvent(
            'show-cellular-setup',
            {detail: {pageName: CellularSetupPageName.ESIM_FLOW_UI}});
        internetPage.dispatchEvent(event);
        await flushTasks();

        assertTrue(internetPage.$.errorToast.open);
        assertEquals(
            internetPage.i18n('eSimNoConnectionErrorToast'),
            internetPage.$.errorToastMessage.innerHTML);

        assertNull(queryCellularSetupDialog());

        // Hide the toast
        internetPage.$.errorToast.hide();
        assertFalse(internetPage.$.errorToast.open);

        // Connect to non-cellular network.
        const wifiNetwork =
            OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi');
        wifiNetwork.connectionState = ConnectionStateType.kOnline;
        mojoApi.addNetworksForTest([wifiNetwork]);
        await flushTasks();

        // Send event, toast should be hidden, dialog open.
        internetPage.dispatchEvent(event);
        await flushTasks();
        assertFalse(internetPage.$.errorToast.open);
        assertTrue(!!queryCellularSetupDialog());
      });

  test('Show toast on show-error-toast event', async () => {
    await init();
    assertFalse(internetPage.$.errorToast.open);

    const message = 'Toast message';
    const event = new CustomEvent('show-error-toast', {detail: message});
    internetPage.dispatchEvent(event);
    await flushTasks();
    assertTrue(internetPage.$.errorToast.open);
    assertEquals(message, internetPage.$.errorToastMessage.innerHTML);
  });

  test('Internet detail menu renders', async () => {
    await navigateToCellularDetailPage();

    const internetDetailMenu =
        internetPage.shadowRoot!.querySelector('settings-internet-detail-menu');
    assertTrue(!!internetDetailMenu);
  });

  test('Update global policy when triggering OnPoliciesApplied()', async () => {
    await navigateToCellularDetailPage();

    const detailPage = internetPage.shadowRoot!.querySelector(
        'settings-internet-detail-subpage');
    assertTrue(!!detailPage);
    assertTrue(!!detailPage.globalPolicy);
    assertFalse(!!detailPage.globalPolicy.allowOnlyPolicyCellularNetworks);

    // Set global policy should also update the global policy
    mojoApi.setGlobalPolicy({
      allowOnlyPolicyCellularNetworks: true,
    } as GlobalPolicy);
    await flushTasks();

    assertTrue(!!detailPage);
    assertTrue(!!detailPage.globalPolicy);
    assertTrue(detailPage.globalPolicy.allowOnlyPolicyCellularNetworks);
  });

  test(
      'Navigating to Known Networks without network-type parameters ' +
          'defaults to Wi-Fi',
      async () => {
        await init();

        const params = new URLSearchParams();
        params.append('type', '');

        // Navigate straight to Known Networks while passing in parameters
        // with an empty type.
        Router.getInstance().navigateTo(routes.KNOWN_NETWORKS, params);
        internetPage.currentRouteChanged(routes.KNOWN_NETWORKS, undefined);

        const knownNetworksPage = internetPage.shadowRoot!.querySelector(
            'settings-internet-known-networks-subpage');
        assertTrue(!!knownNetworksPage);

        // Confirm that the knownNetworkType_ was set to kWiFi.
        assertEquals(NetworkType.kWiFi, knownNetworksPage.networkType);
      });

  test('Navigate to/from APN subpage', async () => {
    loadTimeData.overrideValues({isApnRevampEnabled: true});
    await navigateToApnSubpage();
    assertEquals(Router.getInstance().currentRoute, routes.APN);
    assertTrue(!!internetPage.shadowRoot!.querySelector('apn-subpage'));

    const windowPopstatePromise = eventToPromise('popstate', window);
    Router.getInstance().navigateToPreviousRoute();
    await windowPopstatePromise;
    await waitBeforeNextRender(internetPage);

    const detailPage = internetPage.shadowRoot!.querySelector(
        'settings-internet-detail-subpage');
    assertTrue(!!detailPage);
    await flushTasks();

    assertEquals(
        detailPage.shadowRoot!.querySelector('#apnSubpageButton'),
        detailPage.shadowRoot!.activeElement,
        'Apn subpage row should be focused');
  });

  test(
      'Create apn button opens dialogs and clicking cancel button removes it',
      async () => {
        loadTimeData.overrideValues({isApnRevampEnabled: true});
        await navigateToApnSubpage();
        const subpage = internetPage.shadowRoot!.querySelector('apn-subpage');
        assertTrue(!!subpage);
        const apnList = subpage.shadowRoot!.querySelector('apn-list');
        assertTrue(!!apnList);
        const getApnDetailDialog = () =>
            apnList.shadowRoot!.querySelector('apn-detail-dialog');

        assertNull(getApnDetailDialog());
        const apnMenuButton = queryApnActionMenuButton();
        assertTrue(!!apnMenuButton);
        apnMenuButton.click();
        await flushTasks();

        const apnDotsMenu = queryApnDotsMenu();
        assertTrue(!!apnDotsMenu);
        assertTrue(apnDotsMenu.open);
        const createCustomApnButton = queryCreateCustomApnButton();
        assertTrue(!!createCustomApnButton);
        createCustomApnButton.click();
        await flushTasks();

        assertFalse(apnDotsMenu.open);
        const apnDetailDialog = getApnDetailDialog();
        assertTrue(!!apnDetailDialog);
        const onCloseEventPromise = eventToPromise('close', apnList);
        const cancelButton =
            apnDetailDialog.shadowRoot!.querySelector<HTMLElement>(
                '#apnDetailCancelBtn');
        assertTrue(!!cancelButton);
        cancelButton.click();
        await onCloseEventPromise;

        assertNull(getApnDetailDialog());
      });

  test(
      'Discover more apns button opens dialog and clicking cancel button ' +
          'removes it',
      async () => {
        loadTimeData.overrideValues({isApnRevampEnabled: true});
        await navigateToApnSubpage();
        const subpage = internetPage.shadowRoot!.querySelector('apn-subpage');
        assertTrue(!!subpage);
        const apnList = subpage.shadowRoot!.querySelector('apn-list');
        assertTrue(!!apnList);

        function getApnSelectionDialog() {
          return apnList!.shadowRoot!.querySelector('apn-selection-dialog');
        }

        assertNull(getApnSelectionDialog());
        const apnMenuButton = queryApnActionMenuButton();
        assertTrue(!!apnMenuButton);
        assertEquals(internetPage.i18n('moreActions'), apnMenuButton.title);
        apnMenuButton.click();
        await flushTasks();

        const apnDotsMenu = queryApnDotsMenu();
        assertTrue(!!apnDotsMenu);
        assertTrue(apnDotsMenu.open);
        const discoverMoreApnsButton = queryDiscoverMoreApnsButton();
        assertTrue(!!discoverMoreApnsButton);
        discoverMoreApnsButton.click();
        await flushTasks();

        assertFalse(apnDotsMenu.open);
        const apnSelectionDialog = getApnSelectionDialog();
        assertTrue(!!apnSelectionDialog);
        const onCloseEventPromise = eventToPromise('close', apnList);
        const cancelButton =
            apnSelectionDialog.shadowRoot!.querySelector<HTMLElement>(
                '.cancel-button');
        assertTrue(!!cancelButton);
        cancelButton.click();
        await onCloseEventPromise;

        assertNull(getApnSelectionDialog());
      });

  test('Navigate to APN subpage and remove cellular properties.', async () => {
    loadTimeData.overrideValues({isApnRevampEnabled: true});
    await navigateToApnSubpage();
    assertEquals(routes.APN, Router.getInstance().currentRoute);
    assertTrue(!!internetPage.shadowRoot!.querySelector('apn-subpage'));
    // We use the same guid as in navigateToCellularDetailPage so that
    // we trigger onNetworkStateChanged
    const network = OncMojo.getDefaultManagedProperties(
        NetworkType.kWiFi, 'cellular1', 'name1');
    const windowPopstatePromise = eventToPromise('popstate', window);
    mojoApi.setManagedPropertiesForTest(network);
    await windowPopstatePromise;
    await waitBeforeNextRender(internetPage);
    // Because there were no cellular properties we call apn_subpage close
    // which navigates to the previous page.
    assertEquals(routes.NETWORK_DETAIL, Router.getInstance().currentRoute);
  });

  test(
      'Navigate to APN subpage without providing guid as parameter',
      async () => {
        loadTimeData.overrideValues({isApnRevampEnabled: true});
        await navigateToCellularDetailPage();
        const windowPopstatePromise = eventToPromise('popstate', window);
        Router.getInstance().navigateTo(routes.APN);
        await windowPopstatePromise;
        await waitBeforeNextRender(internetPage);
        assertNotEquals(routes.APN, Router.getInstance().currentRoute);
      });

  ['ltr', 'rtl'].forEach(languageDirection => {
    test(
        'Disable and show tooltip for APN buttons when custom APNs limit is' +
            'reached',
        async () => {
          document.body.style.direction = languageDirection;
          loadTimeData.overrideValues({isApnRevampEnabled: true});
          await navigateToApnSubpage();
          const getCreateCustomApnTooltip = () =>
              internetPage.shadowRoot!.querySelector<PaperTooltipElement>(
                  '#createCustomApnTooltip');
          const getDiscoverMoreApnsTooltip = () =>
              internetPage.shadowRoot!.querySelector<PaperTooltipElement>(
                  '#discoverMoreApnsTooltip');

          const createCustomApnButton = queryCreateCustomApnButton();
          assertTrue(!!createCustomApnButton);
          assertFalse(createCustomApnButton.disabled);
          const discoverMoreApnsButton = queryDiscoverMoreApnsButton();
          assertTrue(!!discoverMoreApnsButton);
          assertFalse(discoverMoreApnsButton.disabled);
          assertNull(getCreateCustomApnTooltip());
          assertNull(getDiscoverMoreApnsTooltip());

          let properties = OncMojo.getDefaultManagedProperties(
              NetworkType.kCellular, 'cellular1', 'cellular');

          // We're setting the list of APNs to the max number
          const customApn = {accessPointName: 'apn'} as ApnProperties;
          properties.typeProperties.cellular!.customApnList =
              Array(MAX_NUM_CUSTOM_APNS).fill({...customApn});
          mojoApi.setManagedPropertiesForTest(properties);
          await flushTasks();

          assertTrue(createCustomApnButton.disabled);
          assertTrue(discoverMoreApnsButton.disabled);
          const createCustomApnTooltip = getCreateCustomApnTooltip();
          assertTrue(!!createCustomApnTooltip);
          assertTrue(createCustomApnTooltip.innerHTML.includes(
              internetPage.i18n('customApnLimitReached')));
          const toolTipPosition =
              languageDirection === 'ltr' ? 'left' : 'right';
          assertEquals(toolTipPosition, createCustomApnTooltip.position);
          const discoverMoreApnsTooltip = getDiscoverMoreApnsTooltip();
          assertTrue(!!discoverMoreApnsTooltip);
          assertTrue(discoverMoreApnsTooltip.innerHTML.includes(
              internetPage.i18n('customApnLimitReached')));
          assertEquals(toolTipPosition, discoverMoreApnsTooltip.position);

          properties = OncMojo.getDefaultManagedProperties(
              NetworkType.kCellular, 'cellular1', 'cellular');
          properties.typeProperties.cellular!.customApnList = [];
          mojoApi.setManagedPropertiesForTest(properties);
          await flushTasks();

          assertFalse(createCustomApnButton.disabled);
          assertFalse(discoverMoreApnsButton.disabled);
          assertNull(getCreateCustomApnTooltip());
          assertNull(getDiscoverMoreApnsTooltip());
        });
  });

  [true, false].forEach(isApnRevampAndAllowApnModificationPolicyEnabled => {
    test(
        `Managed APN UI states when ` +
            `isApnRevampAndAllowApnModificationPolicyEnabled is ${
                isApnRevampAndAllowApnModificationPolicyEnabled}`,
        async () => {
          loadTimeData.overrideValues({
            isApnRevampEnabled: true,
            isApnRevampAndAllowApnModificationPolicyEnabled:
                isApnRevampAndAllowApnModificationPolicyEnabled,
          });
          await navigateToApnSubpage();

          mojoApi.setGlobalPolicy(undefined);
          await flushTasks();

          // Check for APN policies managed icon.
          const getApnManagedIcon = () =>
              internetPage.shadowRoot!.querySelector('#apnManagedIcon');
          const apnActionMenuButton =
              internetPage.shadowRoot!.querySelector<HTMLButtonElement>(
                  '#apnActionMenuButton');
          const apnSubpage =
              internetPage.shadowRoot!.querySelector<ApnSubpageElement>(
                  'apn-subpage');
          assertFalse(!!getApnManagedIcon());
          assert(apnActionMenuButton);
          assertFalse(apnActionMenuButton.disabled);
          assert(apnSubpage);
          assertFalse(apnSubpage.shouldDisallowApnModification);

          let globalPolicy = {
            allowApnModification: true,
          } as GlobalPolicy;
          mojoApi.setGlobalPolicy(globalPolicy);
          await flushTasks();
          assertFalse(!!getApnManagedIcon());
          assertFalse(apnActionMenuButton.disabled);
          assertFalse(apnSubpage.shouldDisallowApnModification);

          globalPolicy = {
            allowApnModification: false,
          } as GlobalPolicy;
          mojoApi.setGlobalPolicy(globalPolicy);
          await flushTasks();
          assertEquals(
              isApnRevampAndAllowApnModificationPolicyEnabled,
              !!getApnManagedIcon());
          assertEquals(
              isApnRevampAndAllowApnModificationPolicyEnabled,
              apnActionMenuButton.disabled);
          assertEquals(
              isApnRevampAndAllowApnModificationPolicyEnabled,
              apnSubpage.shouldDisallowApnModification);
        });
  });

  test('Navigate to Passpoint detail page', async () => {
    const subId = 'a_passpoint_id';
    const sub = {
      id: subId,
      domains: ['passpoint.example.com'],
      friendlyName: 'Passpoint Example Ltd.',
      provisioningSource: 'app.passpoint.example.com',
      trustedCa: '',
      expirationEpochMs: 0n,
    };
    passpointService.addSubscription(sub);
    await init();

    const params = new URLSearchParams();
    params.append('id', subId);

    // Navigate straight to Passpoint detail subpage.
    Router.getInstance().navigateTo(routes.PASSPOINT_DETAIL, params);
    internetPage.currentRouteChanged(routes.PASSPOINT_DETAIL, undefined);

    const passpointDetailPage =
        internetPage.shadowRoot!.querySelector('settings-passpoint-subpage');
    assertTrue(!!passpointDetailPage);
  });

  test('Show spinner on hotspot subpage when enabling', async () => {
    const hotspotInfo = {
      state: HotspotState.kDisabled,
      allowStatus: HotspotAllowStatus.kAllowed,
      clientCount: 0,
      config: {
        ssid: 'test_ssid',
        passphrase: 'test_passphrase',
        autoDisable: true,
        security: WiFiSecurityMode.kWpa2,
        band: WiFiBand.kAutoChoose,
        bssidRandomization: true,
      },
      allowedWifiSecurityModes: [],
    } as HotspotInfo;
    hotspotConfig.setFakeHotspotInfo(hotspotInfo);
    await init();

    Router.getInstance().navigateTo(routes.HOTSPOT_DETAIL);
    await flushTasks();

    const hotspotDetailPage =
        internetPage.shadowRoot!.querySelector('settings-hotspot-subpage');
    assertTrue(!!hotspotDetailPage);

    const hotspotSubpage =
        internetPage.shadowRoot!.querySelector<OsSettingsSubpageElement>(
            'os-settings-subpage#hotspotSubpage');
    assertTrue(!!hotspotSubpage);
    assertFalse(hotspotSubpage.showSpinner);

    hotspotConfig.setFakeHotspotState(HotspotState.kEnabling);
    await flushTasks();
    assertTrue(hotspotSubpage.showSpinner);

    hotspotConfig.setFakeHotspotState(HotspotState.kDisabling);
    await flushTasks();
    assertTrue(hotspotSubpage.showSpinner);
  });

  // TODO(stevenjb): Figure out a way to reliably test navigation. Currently
  // such tests are flaky.
});