chromium/chrome/test/data/webui/chromeos/settings/os_settings_menu/os_settings_menu_revamp_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.

/**
 * @fileoverview Runs tests for the left menu of CrOS Settings, assuming the
 * kOsSettingsRevampWayfinding feature flag is enabled.
 */

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

import {Account, AccountManagerBrowserProxyImpl} from 'chrome://os-settings/lazy_load.js';
import {createPageAvailabilityForTesting, createRouterForTesting, DevicePageBrowserProxyImpl, FakeInputDeviceSettingsProvider, fakeKeyboards, fakeMice, fakePointingSticks, fakeTouchpads, MultiDeviceBrowserProxyImpl, MultiDevicePageContentData, MultiDeviceSettingsMode, OsSettingsMenuElement, OsSettingsMenuItemElement, Router, routesMojom, setInputDeviceSettingsProviderForTesting} from 'chrome://os-settings/os_settings.js';
import {setBluetoothConfigForTesting} from 'chrome://resources/ash/common/bluetooth/cros_bluetooth_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 {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {AudioOutputCapability, DeviceConnectionState, DeviceType} from 'chrome://resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
import {InhibitReason} 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 {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertNull, assertStringContains, assertStringExcludes, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {FakeNetworkConfig} from 'chrome://webui-test/chromeos/fake_network_config_mojom.js';
import {createDefaultBluetoothDevice, FakeBluetoothConfig} from 'chrome://webui-test/cr_components/chromeos/bluetooth/fake_bluetooth_config.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {isVisible} from 'chrome://webui-test/test_util.js';

import {TestDevicePageBrowserProxy} from '../device_page/test_device_page_browser_proxy.js';
import {createFakePageContentData, TestMultideviceBrowserProxy} from '../multidevice_page/test_multidevice_browser_proxy.js';
import {TestAccountManagerBrowserProxy} from '../os_people_page/test_account_manager_browser_proxy.js';
import {clearBody} from '../utils.js';

const {Section} = routesMojom;
type SectionName = keyof typeof Section;

interface MenuItemData {
  sectionName: SectionName;
  path: string;
}

suite('<os-settings-menu>', () => {
  let settingsMenu: OsSettingsMenuElement;
  let accountManagerBrowserProxy: TestAccountManagerBrowserProxy;
  let testRouter: Router;

  async function createMenu(): Promise<void> {
    clearBody();
    settingsMenu = document.createElement('os-settings-menu');
    settingsMenu.pageAvailability = createPageAvailabilityForTesting();
    settingsMenu.advancedOpened = true;
    document.body.appendChild(settingsMenu);
    await flushTasks();
  }

  function queryMenuItemByPath(path: string): OsSettingsMenuItemElement|null {
    return settingsMenu.shadowRoot!.querySelector<OsSettingsMenuItemElement>(
        `os-settings-menu-item[path="${path}"]`);
  }

  function simulateGuestMode(enabled: boolean): void {
    loadTimeData.overrideValues({isGuest: enabled});

    // Reinitialize Router and routes based on load time data. Some routes
    // should not exist in guest mode.
    testRouter = createRouterForTesting();
    Router.resetInstanceForTesting(testRouter);
  }

  function enableInputDeviceSettingsSplit(enabled: boolean): void {
    loadTimeData.overrideValues({enableInputDeviceSettingsSplit: enabled});
  }

  setup(() => {
    loadTimeData.overrideValues({isKerberosEnabled: true});
    simulateGuestMode(/*enabled=*/ false);

    accountManagerBrowserProxy = new TestAccountManagerBrowserProxy();
    AccountManagerBrowserProxyImpl.setInstanceForTesting(
        accountManagerBrowserProxy);
  });

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

  test('Advanced toggle and collapsible menu are not visible', async () => {
    await createMenu();

    const advancedButton =
        settingsMenu.shadowRoot!.querySelector('#advancedButton');
    assertFalse(isVisible(advancedButton));

    const advancedCollapse =
        settingsMenu.shadowRoot!.querySelector('#advancedCollapse');
    assertFalse(isVisible(advancedCollapse));

    const advancedSubmenu =
        settingsMenu.shadowRoot!.querySelector('#advancedSubmenu');
    assertFalse(isVisible(advancedSubmenu));
  });

  suite('All menu items', () => {
    setup(async () => {
      await createMenu();
    });

    const menuItemData: MenuItemData[] = [
      {
        sectionName: 'kNetwork',
        path: `/${routesMojom.NETWORK_SECTION_PATH}`,
      },
      {
        sectionName: 'kBluetooth',
        path: `/${routesMojom.BLUETOOTH_SECTION_PATH}`,
      },
      {
        sectionName: 'kMultiDevice',
        path: `/${routesMojom.MULTI_DEVICE_SECTION_PATH}`,
      },
      {
        sectionName: 'kPeople',
        path: `/${routesMojom.PEOPLE_SECTION_PATH}`,
      },
      {
        sectionName: 'kKerberos',
        path: `/${routesMojom.KERBEROS_SECTION_PATH}`,
      },
      {
        sectionName: 'kDevice',
        path: `/${routesMojom.DEVICE_SECTION_PATH}`,
      },
      {
        sectionName: 'kPersonalization',
        path: `/${routesMojom.PERSONALIZATION_SECTION_PATH}`,
      },
      {
        sectionName: 'kPrivacyAndSecurity',
        path: `/${routesMojom.PRIVACY_AND_SECURITY_SECTION_PATH}`,
      },
      {
        sectionName: 'kApps',
        path: `/${routesMojom.APPS_SECTION_PATH}`,
      },
      {
        sectionName: 'kAccessibility',
        path: `/${routesMojom.ACCESSIBILITY_SECTION_PATH}`,
      },
      {
        sectionName: 'kSystemPreferences',
        path: `/${routesMojom.SYSTEM_PREFERENCES_SECTION_PATH}`,
      },
    ];

    for (const {sectionName, path} of menuItemData) {
      suite(`When ${sectionName} page is available`, () => {
        setup(() => {
          settingsMenu.pageAvailability = {
            ...settingsMenu.pageAvailability,
            [Section[sectionName]]: true,
          };
          flush();
        });

        test(`${sectionName} menu item is visible`, () => {
          const menuItem = queryMenuItemByPath(path);
          assertTrue(isVisible(menuItem));
        });


        test(`${sectionName} menu item is selected when route changes`, () => {
          const route = testRouter.getRouteForPath(path);
          assertTrue(!!route);
          testRouter.navigateTo(route);
          flush();

          const menuItem = queryMenuItemByPath(path);
          assertTrue(!!menuItem);
          assertEquals('true', menuItem.getAttribute('aria-current'));
          assertTrue(menuItem.classList.contains('iron-selected'));
        });
      });

      suite(`When ${sectionName} page is not available`, () => {
        setup(() => {
          settingsMenu.pageAvailability = {
            ...settingsMenu.pageAvailability,
            [Section[sectionName]]: false,
          };
          flush();
        });

        test(`${sectionName} menu item is not visible`, () => {
          const menuItem = queryMenuItemByPath(path);
          assertFalse(isVisible(menuItem));
        });
      });
    }
  });

  suite('About ChromeOS menu item', () => {
    test('About page menu item should always be visible', async () => {
      await createMenu();
      const menuItem =
          queryMenuItemByPath(`/${routesMojom.ABOUT_CHROME_OS_SECTION_PATH}`);
      assertTrue(isVisible(menuItem));
    });

    test('Description text', async () => {
      await createMenu();

      const aboutMenuItem =
          queryMenuItemByPath(`/${routesMojom.ABOUT_CHROME_OS_SECTION_PATH}`);
      assertTrue(!!aboutMenuItem);

      assertEquals('Updates, help, developer options', aboutMenuItem.sublabel);
    });
  });

  suite('Accounts menu item', () => {
    const fakeAccounts: Account[] = [
      {
        id: '123',
        accountType: 1,
        isDeviceAccount: true,
        isSignedIn: true,
        unmigrated: false,
        isManaged: false,
        fullName: 'Jon Snow',
        pic: 'data:image/png;base64,primaryAccountPicData',
        email: '[email protected]',
        isAvailableInArc: true,
        organization: 'Stark',
      },
      {
        id: '456',
        accountType: 1,
        isDeviceAccount: false,
        isSignedIn: true,
        unmigrated: false,
        isManaged: false,
        fullName: 'Daenerys Targaryen',
        pic: 'data:image/png;base64,primaryAccountPicData',
        email: '[email protected]',
        isAvailableInArc: true,
        organization: 'Targaryen',
      },
    ];

    function getAccountsMenuItem(): OsSettingsMenuItemElement {
      const accountsMenuItem =
          queryMenuItemByPath(`/${routesMojom.PEOPLE_SECTION_PATH}`);
      assertTrue(!!accountsMenuItem);
      return accountsMenuItem;
    }

    suite('when in guest mode', () => {
      setup(() => {
        simulateGuestMode(/*enabled=*/ true);
      });

      test('Accounts menu item should not exist', async () => {
        await createMenu();
        const accountsMenuItem =
            queryMenuItemByPath(`/${routesMojom.PEOPLE_SECTION_PATH}`);
        assertNull(accountsMenuItem);
      });

      test('Should not call getAccounts', async () => {
        await createMenu();

        const callCount =
            accountManagerBrowserProxy.getCallCount('getAccounts');
        assertEquals(0, callCount);
      });
    });

    suite('When there is only one account', () => {
      setup(() => {
        accountManagerBrowserProxy.setAccountsForTesting(
            fakeAccounts.slice(0, 1));
      });

      test('Description should show account email', async () => {
        await createMenu();
        await accountManagerBrowserProxy.whenCalled('getAccounts');

        const accountsMenuItem = getAccountsMenuItem();
        assertEquals(fakeAccounts[0]!.email, accountsMenuItem.sublabel);
      });

      test('Description should update when an account is added', async () => {
        await createMenu();
        await accountManagerBrowserProxy.whenCalled('getAccounts');

        const accountsMenuItem = getAccountsMenuItem();
        assertEquals(fakeAccounts[0]!.email, accountsMenuItem.sublabel);

        // Update accounts to have 2 accounts
        accountManagerBrowserProxy.setAccountsForTesting(fakeAccounts);
        webUIListenerCallback('accounts-changed');
        await flushTasks();

        assertEquals('2 accounts', accountsMenuItem.sublabel);
      });
    });

    suite('When there is more than one account', () => {
      setup(() => {
        accountManagerBrowserProxy.setAccountsForTesting(fakeAccounts);
      });

      test('Description should show number of accounts', async () => {
        await createMenu();
        await accountManagerBrowserProxy.whenCalled('getAccounts');

        const accountsMenuItem = getAccountsMenuItem();
        assertEquals('2 accounts', accountsMenuItem.sublabel);
      });

      test('Description should update when an account is removed', async () => {
        await createMenu();
        await accountManagerBrowserProxy.whenCalled('getAccounts');

        const accountsMenuItem = getAccountsMenuItem();
        assertEquals('2 accounts', accountsMenuItem.sublabel);

        // Remove an account to leave only 1 account
        accountManagerBrowserProxy.setAccountsForTesting(
            fakeAccounts.slice(0, 1));
        webUIListenerCallback('accounts-changed');
        await flushTasks();

        assertEquals(fakeAccounts[0]!.email, accountsMenuItem.sublabel);
      });
    });
  });

  suite('Apps menu item', () => {
    test('Description text when ARC is enabled', async () => {
      loadTimeData.overrideValues({androidAppsVisible: true});
      await createMenu();

      const appsMenuItem =
          queryMenuItemByPath(`/${routesMojom.APPS_SECTION_PATH}`);
      assertTrue(!!appsMenuItem);

      assertEquals('Notifications, Google Play', appsMenuItem.sublabel);
    });

    test('Description test when ARC is not enabled', async () => {
      loadTimeData.overrideValues({androidAppsVisible: false});
      await createMenu();

      const appsMenuItem =
          queryMenuItemByPath(`/${routesMojom.APPS_SECTION_PATH}`);
      assertTrue(!!appsMenuItem);

      assertEquals('Notifications', appsMenuItem.sublabel);
    });
  });

  suite('A11y menu item', () => {
    test('Description text', async () => {
      await createMenu();

      const a11yMenuItem =
          queryMenuItemByPath(`/${routesMojom.ACCESSIBILITY_SECTION_PATH}`);
      assertTrue(!!a11yMenuItem);

      assertEquals('Screen reader, magnification', a11yMenuItem.sublabel);
    });
  });

  suite('Bluetooth menu item', () => {
    let bluetoothConfig: FakeBluetoothConfig;
    const bluetoothMouse = createDefaultBluetoothDevice(
        '111111',
        /*publicName=*/ 'Bluetooth Mouse',
        /*connectionState=*/ DeviceConnectionState.kConnected,
        /*opt_nickname=*/ 'My Bluetooth Mouse',
        /*opt_audioCapability=*/ AudioOutputCapability.kNotCapableOfAudioOutput,
        /*opt_deviceType=*/ DeviceType.kMouse);
    const bluetoothHeadphones = createDefaultBluetoothDevice(
        '222222',
        /*publicName=*/ 'Bluetooth Headphones',
        /*connectionState=*/ DeviceConnectionState.kConnected,
        /*opt_nickname=*/ 'My Beats',
        /*opt_audioCapability=*/ AudioOutputCapability.kCapableOfAudioOutput,
        /*opt_deviceType=*/ DeviceType.kHeadset);

    function getBluetoothMenuItem(): OsSettingsMenuItemElement {
      const bluetoothMenuItem =
          queryMenuItemByPath(`/${routesMojom.BLUETOOTH_SECTION_PATH}`);
      assertTrue(!!bluetoothMenuItem);
      return bluetoothMenuItem;
    }

    setup(() => {
      bluetoothConfig = new FakeBluetoothConfig();
      bluetoothConfig.setBluetoothEnabledState(/*enabled=*/ true);
      setBluetoothConfigForTesting(bluetoothConfig);
    });

    test('Description shows "Off" when bluetooth is off', async () => {
      bluetoothConfig.setBluetoothEnabledState(/*enabled=*/ false);
      await createMenu();

      const bluetoothMenuItem = getBluetoothMenuItem();
      assertEquals('Off', bluetoothMenuItem.sublabel);
    });

    test(
        'Description shows "On" when bluetooth is on and no devices are connected',
        async () => {
          await createMenu();

          const bluetoothMenuItem = getBluetoothMenuItem();
          assertEquals('On', bluetoothMenuItem.sublabel);
        });

    test(
        'Description shows device name for a single connected device',
        async () => {
          bluetoothConfig.appendToPairedDeviceList([bluetoothMouse]);
          await createMenu();

          const bluetoothMenuItem = getBluetoothMenuItem();
          assertEquals(bluetoothMouse.nickname, bluetoothMenuItem.sublabel);
        });

    test('Description shows that multiple devices are connected', async () => {
      bluetoothConfig.appendToPairedDeviceList(
          [bluetoothMouse, bluetoothHeadphones]);
      await createMenu();

      const bluetoothMenuItem = getBluetoothMenuItem();
      assertEquals('2 devices connected', bluetoothMenuItem.sublabel);
    });

    test('Description updates when connected devices change', async () => {
      await createMenu();

      const bluetoothMenuItem = getBluetoothMenuItem();
      assertEquals('On', bluetoothMenuItem.sublabel);

      // Connect a bluetooth mouse.
      bluetoothConfig.appendToPairedDeviceList([bluetoothMouse]);
      await flushTasks();
      assertEquals(bluetoothMouse.nickname, bluetoothMenuItem.sublabel);

      // Connect bluetooth headphones.
      bluetoothConfig.appendToPairedDeviceList([bluetoothHeadphones]);
      await flushTasks();
      assertEquals('2 devices connected', bluetoothMenuItem.sublabel);

      // Disconnect the bluetooth mouse.
      bluetoothConfig.removePairedDevice(bluetoothMouse);
      await flushTasks();
      assertEquals(bluetoothHeadphones.nickname, bluetoothMenuItem.sublabel);

      // Disconnect the bluetooth headphones.
      bluetoothConfig.removePairedDevice(bluetoothHeadphones);
      await flushTasks();
      assertEquals('On', bluetoothMenuItem.sublabel);

      bluetoothConfig.setBluetoothEnabledState(/*enabled=*/ false);
      await flushTasks();
      assertEquals('Off', bluetoothMenuItem.sublabel);
    });
  });

  suite('Device menu item', () => {
    let provider: FakeInputDeviceSettingsProvider;

    function getDeviceMenuItem(): OsSettingsMenuItemElement {
      const deviceMenuItem =
          queryMenuItemByPath(`/${routesMojom.DEVICE_SECTION_PATH}`);
      assertTrue(!!deviceMenuItem);
      return deviceMenuItem;
    }

    setup(() => {
      enableInputDeviceSettingsSplit(/*enabled=*/ true);

      provider = new FakeInputDeviceSettingsProvider();
      provider.setFakeKeyboards([]);
      provider.setFakeMice([]);
      provider.setFakePointingSticks([]);
      provider.setFakeTouchpads([]);
      setInputDeviceSettingsProviderForTesting(provider);
    });

    test('Description includes "keyboard" when connected', async () => {
      await createMenu();

      // No keyboard connected.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'keyboard');

      // Connect a keyboard.
      provider.setFakeKeyboards(fakeKeyboards);
      flush();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'keyboard');

      // Disconnect the keyboard.
      provider.setFakeKeyboards([]);
      flush();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'keyboard');
    });

    test('Description includes "mouse" when connected', async () => {
      await createMenu();

      // No mouse connected.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

      // Connect a mouse.
      provider.setFakeMice(fakeMice);
      flush();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

      // Disconnect the mouse.
      provider.setFakeMice([]);
      flush();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
    });

    test(
        'Description includes "mouse" when pointing stick is connected',
        async () => {
          await createMenu();

          // No pointing stick connected.
          const deviceMenuItem = getDeviceMenuItem();
          assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

          // Connect a pointing stick.
          provider.setFakePointingSticks(fakePointingSticks);
          flush();
          assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

          // Disconnect the pointing stick.
          provider.setFakePointingSticks([]);
          flush();
          assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
        });

    test('Description includes "touchpad" when connected', async () => {
      await createMenu();

      // No touchpad connected.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Connect a touchpad.
      provider.setFakeTouchpads(fakeTouchpads);
      flush();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Disconnect the touchpad.
      provider.setFakeTouchpads([]);
      flush();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');
    });

    test('Description prioritizes "mouse" over "touchpad"', async () => {
      await createMenu();

      // No mouse or touchpad connected.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Connect a touchpad.
      provider.setFakeTouchpads(fakeTouchpads);
      flush();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Connect a mouse.
      provider.setFakeMice(fakeMice);
      flush();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');
    });

    test('Description includes print and display by default', async () => {
      await createMenu();
      const deviceMenuItem = getDeviceMenuItem();
      assertEquals('Print, display', deviceMenuItem.sublabel);
    });

    test(
        'Description shows at most 3 words when devices are connected',
        async () => {
          // 4 devices connected.
          provider.setFakeKeyboards(fakeKeyboards);
          provider.setFakeMice(fakeMice);
          provider.setFakePointingSticks(fakePointingSticks);
          provider.setFakeTouchpads(fakeTouchpads);

          await createMenu();
          const deviceMenuItem = getDeviceMenuItem();
          assertEquals('Keyboard, mouse, print', deviceMenuItem.sublabel);
        });

    test('Description shows "Keyboard, print, display', async () => {
      provider.setFakeKeyboards(fakeKeyboards);
      provider.setFakeMice([]);
      provider.setFakePointingSticks([]);
      provider.setFakeTouchpads([]);

      await createMenu();
      const deviceMenuItem = getDeviceMenuItem();
      assertEquals('Keyboard, print, display', deviceMenuItem.sublabel);
    });

    test('Description shows "Mouse, print, display"', async () => {
      provider.setFakeKeyboards([]);
      provider.setFakeMice(fakeMice);
      provider.setFakePointingSticks([]);
      provider.setFakeTouchpads(fakeTouchpads);

      await createMenu();
      const deviceMenuItem = getDeviceMenuItem();
      assertEquals('Mouse, print, display', deviceMenuItem.sublabel);

      // Still show "mouse" if pointing stick is connected.
      provider.setFakeKeyboards([]);
      provider.setFakeMice([]);
      provider.setFakePointingSticks(fakePointingSticks);
      provider.setFakeTouchpads(fakeTouchpads);
      flush();

      assertEquals('Mouse, print, display', deviceMenuItem.sublabel);
    });

    test('Description shows "Touchpad, print, display', async () => {
      provider.setFakeKeyboards([]);
      provider.setFakeMice([]);
      provider.setFakePointingSticks([]);
      provider.setFakeTouchpads(fakeTouchpads);

      await createMenu();
      const deviceMenuItem = getDeviceMenuItem();
      assertEquals('Touchpad, print, display', deviceMenuItem.sublabel);
    });
  });

  suite('Device menu item settings split disabled', () => {
    let devicePageBrowserProxy: TestDevicePageBrowserProxy;

    function getDeviceMenuItem(): OsSettingsMenuItemElement {
      const deviceMenuItem =
          queryMenuItemByPath(`/${routesMojom.DEVICE_SECTION_PATH}`);
      assertTrue(!!deviceMenuItem);
      return deviceMenuItem;
    }

    setup(() => {
      enableInputDeviceSettingsSplit(/*enabled=*/ false);

      devicePageBrowserProxy = new TestDevicePageBrowserProxy();
      DevicePageBrowserProxyImpl.setInstanceForTesting(devicePageBrowserProxy);

      // Start with all devices disconnected.
      devicePageBrowserProxy.hasMouse = false;
      devicePageBrowserProxy.hasTouchpad = false;
      devicePageBrowserProxy.hasPointingStick = false;
      flush();
    });

    test('Description includes "keyboard" always', async () => {
      await createMenu();

      // Label should always include keyboard.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'keyboard');
    });

    test('Description includes "mouse" when connected', async () => {
      await createMenu();

      // No mouse connected.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

      // Connect a mouse.
      devicePageBrowserProxy.hasMouse = true;
      flush();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

      // Disconnect the mouse.
      devicePageBrowserProxy.hasMouse = false;
      flush();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
    });

    test(
        'Description includes "mouse" when pointing stick is connected',
        async () => {
          await createMenu();

          // No pointing stick connected.
          const deviceMenuItem = getDeviceMenuItem();
          assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

          // Connect a pointing stick.
          devicePageBrowserProxy.hasPointingStick = true;
          flush();
          assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'mouse');

          // Disconnect the pointing stick.
          devicePageBrowserProxy.hasPointingStick = false;
          flush();
          assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
        });

    test('Description includes "touchpad" when connected', async () => {
      await createMenu();

      // No touchpad connected.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Connect a touchpad.
      devicePageBrowserProxy.hasTouchpad = true;
      flush();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Disconnect the touchpad.
      devicePageBrowserProxy.hasTouchpad = false;
      flush();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');
    });

    test('Description prioritizes "mouse" over "touchpad"', async () => {
      await createMenu();

      // No mouse or touchpad connected.
      const deviceMenuItem = getDeviceMenuItem();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Connect a touchpad.
      devicePageBrowserProxy.hasTouchpad = true;
      flush();
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');

      // Connect a mouse.
      devicePageBrowserProxy.hasMouse = true;
      flush();
      assertStringContains(deviceMenuItem.sublabel.toLowerCase(), 'mouse');
      assertStringExcludes(deviceMenuItem.sublabel.toLowerCase(), 'touchpad');
    });

    test(
        'Description shows at most 3 words when devices are connected',
        async () => {
          // 4 devices connected.
          devicePageBrowserProxy.hasMouse = true;
          devicePageBrowserProxy.hasPointingStick = true;
          devicePageBrowserProxy.hasTouchpad = true;
          flush();

          await createMenu();
          const deviceMenuItem = getDeviceMenuItem();
          assertEquals('Keyboard, mouse, print', deviceMenuItem.sublabel);
        });
  });

  suite('Internet menu item', () => {
    let networkConfigRemote: FakeNetworkConfig;
    const ethernetNetwork =
        OncMojo.getDefaultNetworkState(NetworkType.kEthernet, 'ethernet');
    const wifiNetwork =
        OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi');
    const tetherNetwork =
        OncMojo.getDefaultNetworkState(NetworkType.kTether, 'tether');
    const vpnNetwork = OncMojo.getDefaultNetworkState(NetworkType.kVPN, 'vpn');

    function getInternetMenuItem(): OsSettingsMenuItemElement {
      const internetMenuItem =
          queryMenuItemByPath(`/${routesMojom.NETWORK_SECTION_PATH}`);
      assertTrue(!!internetMenuItem);
      return internetMenuItem;
    }

    async function addConnectedNetworks(networkTypes: NetworkType[]):
        Promise<void> {
      const networkStates: OncMojo.NetworkStateProperties[] = [];
      for (const type of networkTypes) {
        switch (type) {
          case NetworkType.kEthernet:
            networkStates.push({
              ...ethernetNetwork,
              connectionState: ConnectionStateType.kConnected,
            });
            break;
          case NetworkType.kWiFi:
            networkStates.push({
              ...wifiNetwork,
              connectionState: ConnectionStateType.kConnected,
            });
            break;
          case NetworkType.kTether:
            networkStates.push({
              ...tetherNetwork,
              connectionState: ConnectionStateType.kConnected,
            });
            break;
          case NetworkType.kVPN:
            networkStates.push({
              ...vpnNetwork,
              connectionState: ConnectionStateType.kConnected,
            });
            break;
          default:
            break;
        }
      }
      networkConfigRemote.addNetworksForTest(networkStates);
      await flushTasks();
    }

    function getCellularDeviceStateProps(): OncMojo.DeviceStateProperties {
      return {
        ipv4Address: undefined,
        ipv6Address: undefined,
        imei: undefined,
        macAddress: undefined,
        scanning: false,
        simLockStatus: undefined,
        simInfos: undefined,
        inhibitReason: InhibitReason.kNotInhibited,
        simAbsent: false,
        deviceState: DeviceStateType.kDisabled,
        type: NetworkType.kCellular,
        managedNetworkAvailable: false,
        serial: undefined,
        isCarrierLocked: false,
        isFlashing: false,
      };
    }

    setup(() => {
      networkConfigRemote = new FakeNetworkConfig();
      MojoInterfaceProviderImpl.getInstance().setMojoServiceRemoteForTest(
          networkConfigRemote);
    });

    test('Ethernet takes priority when it is connected', async () => {
      await createMenu();

      await addConnectedNetworks([
        NetworkType.kEthernet,
        NetworkType.kWiFi,
        NetworkType.kTether,
        NetworkType.kVPN,
      ]);

      const internetMenuItem = getInternetMenuItem();
      assertEquals(ethernetNetwork.name, internetMenuItem.sublabel);
    });

    test(
        'Wifi takes priority when ethernet network is not connected',
        async () => {
          await createMenu();

          await addConnectedNetworks(
              [NetworkType.kWiFi, NetworkType.kTether, NetworkType.kVPN]);

          const internetMenuItem = getInternetMenuItem();
          assertEquals(wifiNetwork.name, internetMenuItem.sublabel);
        });

    test(
        'Tether takes priority when neither ethernet nor wifi network is connected',
        async () => {
          await createMenu();

          await addConnectedNetworks([NetworkType.kTether, NetworkType.kVPN]);

          const internetMenuItem = getInternetMenuItem();
          assertEquals(tetherNetwork.name, internetMenuItem.sublabel);
        });

    test('VPN shows when it is the only network connected', async () => {
      await createMenu();

      await addConnectedNetworks([NetworkType.kVPN]);

      const internetMenuItem = getInternetMenuItem();
      assertEquals(vpnNetwork.name, internetMenuItem.sublabel);
    });

    test(
        'Instant hotspot available shows when no networks connect but hotspot is avaiable',
        async () => {
          await createMenu();

          // Add tether network state with connection state kNotConnected and
          // enable tether device state. Thus "Instant hotspot available" should
          // show as the description.
          networkConfigRemote.addNetworksForTest([{
            ...tetherNetwork,
            connectionState: ConnectionStateType.kNotConnected,
          }]);
          networkConfigRemote.setNetworkTypeEnabledState(
              NetworkType.kTether, true);
          await flushTasks();

          const internetMenuItem = getInternetMenuItem();
          assertEquals('Instant hotspot available', internetMenuItem.sublabel);
        });


    test(
        'Wifi internet description shows when no network connected and the device does not support mobile data',
        async () => {
          await createMenu();

          const internetMenuItem = getInternetMenuItem();
          assertEquals('Wi-Fi', internetMenuItem.sublabel);
        });

    test(
        'Wifi and mobile data internet description shows when no network connected but the device supports mobile data',
        async () => {
          networkConfigRemote.setDeviceStateForTest({
            ...getCellularDeviceStateProps(),
          });

          await createMenu();

          const internetMenuItem = getInternetMenuItem();
          assertEquals('Wi-Fi, mobile data', internetMenuItem.sublabel);
        });

    test(
        'Internet description updates dynamically on networks connection updates',
        async () => {
          networkConfigRemote.setDeviceStateForTest({
            ...getCellularDeviceStateProps(),
          });

          await createMenu();
          const internetMenuItem = getInternetMenuItem();

          await addConnectedNetworks([NetworkType.kTether]);

          // Tether network is the only connected network, internet description
          // is the name of the Tether network.
          assertEquals(tetherNetwork.name, internetMenuItem.sublabel);

          // Connect the Wi-Fi network, now Wi-Fi network takes priority.
          await addConnectedNetworks([NetworkType.kWiFi]);
          assertEquals(wifiNetwork.name, internetMenuItem.sublabel);

          // Connect the Ethernet network and remove Wi-Fi network. The Ethernet
          // network should take priority.
          await addConnectedNetworks([NetworkType.kEthernet]);
          networkConfigRemote.removeNetworkForTest(wifiNetwork);
          await flushTasks();
          assertEquals(ethernetNetwork.name, internetMenuItem.sublabel);

          // Remove the Ethernet network and connect the VPN network. The Tether
          // network should take priority now.
          await addConnectedNetworks([NetworkType.kVPN]);
          networkConfigRemote.removeNetworkForTest(ethernetNetwork);
          await flushTasks();
          assertEquals(tetherNetwork.name, internetMenuItem.sublabel);

          // Remove the Tether network. VPN network is the only connected
          // network now.
          networkConfigRemote.removeNetworkForTest(tetherNetwork);
          await flushTasks();
          assertEquals(vpnNetwork.name, internetMenuItem.sublabel);

          // Remove the VPN network and enable Tether device state. Add Tether
          // network with connection state kNotConnected. "Instant hotspot
          // available" should be shown.
          networkConfigRemote.removeNetworkForTest(vpnNetwork);
          networkConfigRemote.addNetworksForTest([{
            ...tetherNetwork,
            connectionState: ConnectionStateType.kNotConnected,
          }]);
          networkConfigRemote.setNetworkTypeEnabledState(
              NetworkType.kTether, true);
          await flushTasks();
          assertEquals('Instant hotspot available', internetMenuItem.sublabel);

          // Remove the tether network, the default description "Wi-Fi, mobile
          // data" should be shown.
          networkConfigRemote.removeNetworkForTest(tetherNetwork);
          await flushTasks();
          assertEquals('Wi-Fi, mobile data', internetMenuItem.sublabel);
        });
  });

  suite('Multidevice menu item', () => {
    let multideviceBrowserProxy: TestMultideviceBrowserProxy;

    function getMultideviceMenuItem(): OsSettingsMenuItemElement {
      const multideviceMenuItem =
          queryMenuItemByPath(`/${routesMojom.MULTI_DEVICE_SECTION_PATH}`);
      assertTrue(!!multideviceMenuItem);
      return multideviceMenuItem;
    }

    /**
     * Sets pageContentData via WebUI Listener and flushes.
     */
    function setPageContentData(newPageContentData: MultiDevicePageContentData):
        void {
      webUIListenerCallback(
          'settings.updateMultidevicePageContentData', newPageContentData);
      flush();
    }

    setup(() => {
      multideviceBrowserProxy = new TestMultideviceBrowserProxy();
      MultiDeviceBrowserProxyImpl.setInstanceForTesting(
          multideviceBrowserProxy);
    });

    suite('when in guest mode', () => {
      setup(() => {
        simulateGuestMode(/*enabled=*/ true);
      });

      test('Multidevice menu item should not exist', async () => {
        await createMenu();
        const multideviceMenuItem =
            queryMenuItemByPath(`/${routesMojom.MULTI_DEVICE_SECTION_PATH}`);
        assertNull(multideviceMenuItem);
      });

      test('Should not call getPageContentData', async () => {
        await createMenu();

        const numGetPageContentDataCalls =
            multideviceBrowserProxy.getCallCount('getPageContentData');
        assertEquals(0, numGetPageContentDataCalls);
      });
    });

    test('Default description shows for NO_ELIGIBLE_HOSTS status', async () => {
      await createMenu();

      const multideviceMenuItem = getMultideviceMenuItem();

      setPageContentData(
          createFakePageContentData(MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS));
      assertEquals(
          settingsMenu.i18n('multideviceMenuItemDescription'),
          multideviceMenuItem.sublabel);
    });


    test('Default description shows for NO_HOST_SET status', async () => {
      await createMenu();

      const multideviceMenuItem = getMultideviceMenuItem();

      setPageContentData(
          createFakePageContentData(MultiDeviceSettingsMode.NO_HOST_SET));
      assertEquals(
          settingsMenu.i18n('multideviceMenuItemDescription'),
          multideviceMenuItem.sublabel);
    });

    test(
        'Default description shows for HOST_SET_WAITING_FOR_SERVER status',
        async () => {
          await createMenu();

          const multideviceMenuItem = getMultideviceMenuItem();

          setPageContentData(createFakePageContentData(
              MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER));
          assertEquals(
              settingsMenu.i18n('multideviceMenuItemDescription'),
              multideviceMenuItem.sublabel);
        });

    test(
        'Default description shows for HOST_SET_WAITING_FOR_VERIFICATION status',
        async () => {
          await createMenu();

          const multideviceMenuItem = getMultideviceMenuItem();

          setPageContentData(createFakePageContentData(
              MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION));
          assertEquals(
              settingsMenu.i18n('multideviceMenuItemDescription'),
              multideviceMenuItem.sublabel);
        });

    test(
        'Phone connected description shows for HOST_SET_VERIFIED status',
        async () => {
          await createMenu();

          const multideviceMenuItem = getMultideviceMenuItem();

          const deviceName = 'Google pixel phone';
          setPageContentData(createFakePageContentData(
              MultiDeviceSettingsMode.HOST_SET_VERIFIED, deviceName));
          assertEquals(
              `Connected to ${deviceName}`, multideviceMenuItem.sublabel);
        });

    test(
        'Android phone connected description shows when the device name is missing',
        async () => {
          await createMenu();

          const multideviceMenuItem = getMultideviceMenuItem();

          const pageContentData = createFakePageContentData(
              MultiDeviceSettingsMode.HOST_SET_VERIFIED);
          pageContentData.hostDeviceName = '';
          setPageContentData(pageContentData);
          assertEquals(
              `Connected to Android phone`, multideviceMenuItem.sublabel);
        });

    test(
        'Multidevice menu item description updates on device connection changes',
        async () => {
          await createMenu();

          const multideviceMenuItem = getMultideviceMenuItem();

          // No eligible device found, show the default description "Phone Hub,
          // Quick Share".
          setPageContentData(createFakePageContentData(
              MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS));
          assertEquals(
              settingsMenu.i18n('multideviceMenuItemDescription'),
              multideviceMenuItem.sublabel);

          // No device connected, show the default description "Phone Hub,
          // Quick Share".
          setPageContentData(
              createFakePageContentData(MultiDeviceSettingsMode.NO_HOST_SET));
          assertEquals(
              settingsMenu.i18n('multideviceMenuItemDescription'),
              multideviceMenuItem.sublabel);

          // Device connection is waiting for server, show the default
          // description "Phone Hub, Quick Share".
          setPageContentData(createFakePageContentData(
              MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER));
          assertEquals(
              settingsMenu.i18n('multideviceMenuItemDescription'),
              multideviceMenuItem.sublabel);

          // Device connection is waiting for verification, show the default
          // description "Phone Hub, Quick Share".
          setPageContentData(createFakePageContentData(
              MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION));
          assertEquals(
              settingsMenu.i18n('multideviceMenuItemDescription'),
              multideviceMenuItem.sublabel);

          // Device is connected, show the phone connected description
          // "Connected to <phone name>".
          const deviceName = 'Google pixel phone';
          setPageContentData(createFakePageContentData(
              MultiDeviceSettingsMode.HOST_SET_VERIFIED, deviceName));
          assertEquals(
              `Connected to ${deviceName}`, multideviceMenuItem.sublabel);

          // Disconnect the Device, the description should be updated to the
          // default "Phone Hub, Quick Share".
          setPageContentData(
              createFakePageContentData(MultiDeviceSettingsMode.NO_HOST_SET));
          assertEquals(
              settingsMenu.i18n('multideviceMenuItemDescription'),
              multideviceMenuItem.sublabel);
        });
  });

  suite('Personalization menu item', () => {
    function getPersonalizationMenuItem(): OsSettingsMenuItemElement {
      const menuItem =
          queryMenuItemByPath(`/${routesMojom.PERSONALIZATION_SECTION_PATH}`);
      assertTrue(!!menuItem);
      return menuItem;
    }

    test('Description reflects load time string', async () => {
      await createMenu();

      const menuItem = getPersonalizationMenuItem();
      assertEquals(
          settingsMenu.i18n('personalizationMenuItemDescription'),
          menuItem.sublabel);
    });
  });

  suite('Privacy menu item', () => {
    test('Privacy menu item description', async () => {
      await createMenu();

      const privacyMenuItem = queryMenuItemByPath(
          `/${routesMojom.PRIVACY_AND_SECURITY_SECTION_PATH}`);
      assertTrue(!!privacyMenuItem);

      assertEquals('Lock screen, controls', privacyMenuItem.sublabel);
    });
  });

  suite('System preferences menu item', () => {
    test('Description text', async () => {
      await createMenu();

      const systemPreferencesMenuItem = queryMenuItemByPath(
          `/${routesMojom.SYSTEM_PREFERENCES_SECTION_PATH}`);
      assertTrue(!!systemPreferencesMenuItem);

      assertEquals(
          'Storage, power, language', systemPreferencesMenuItem.sublabel);
    });
  });
});