chromium/chrome/test/data/webui/chromeos/settings/multidevice_page/multidevice_page_test.ts

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

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

import {SettingsMultideviceSubpageElement} from 'chrome://os-settings/lazy_load.js';
import {MultiDeviceBrowserProxyImpl, MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessStatus, Router, routes, setContactManagerForTesting, setNearbyShareSettingsForTesting, settingMojom, SettingsMultidevicePageElement} from 'chrome://os-settings/os_settings.js';
import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {getDeepActiveElement} from 'chrome://resources/js/util.js';
import {Visibility} from 'chrome://resources/mojo/chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom-webui.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertNotEquals, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {FakeContactManager} from 'chrome://webui-test/nearby_share/shared/fake_nearby_contact_manager.js';
import {FakeNearbyShareSettings} from 'chrome://webui-test/nearby_share/shared/fake_nearby_share_settings.js';
import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';

import {createFakePageContentData, HOST_DEVICE, TestMultideviceBrowserProxy} from './test_multidevice_browser_proxy.js';

suite('<settings-multidevice-page>', () => {
  const isRevampWayfindingEnabled =
      loadTimeData.getBoolean('isRevampWayfindingEnabled');
  let multidevicePage: SettingsMultidevicePageElement;
  let browserProxy: TestMultideviceBrowserProxy;
  let ALL_MODES: MultiDeviceSettingsMode[];
  let fakeContactManager: FakeContactManager;
  let fakeSettings: FakeNearbyShareSettings;

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

  /**
   * Sets screen lock status via WebUI Listener and flushes.
   */
  function setScreenLockStatus(
      chromeStatus: boolean, phoneStatus: boolean): void {
    webUIListenerCallback('settings.OnEnableScreenLockChanged', chromeStatus);
    webUIListenerCallback('settings.OnScreenLockStatusChanged', phoneStatus);
    flush();
  }

  /**
   * Sets pageContentData to the specified mode. If it is a mode corresponding
   * to a set host, it will set the hostDeviceName to the provided name or else
   * default to HOST_DEVICE.
   * @param newHostDeviceName Overrides default if |newMode|
   *     corresponds to a set host.
   */
  function setHostData(
      newMode: MultiDeviceSettingsMode, newHostDeviceName?: string): void {
    setPageContentData(createFakePageContentData(newMode, newHostDeviceName));
  }

  function setSuiteState(newState: MultiDeviceFeatureState): void {
    setPageContentData({
      ...multidevicePage.pageContentData,
      betterTogetherState: newState,
    });
  }

  function setSmartLockState(newState: MultiDeviceFeatureState): void {
    setPageContentData({
      ...multidevicePage.pageContentData,
      smartLockState: newState,
    });
  }

  function setPhoneHubNotificationsState(newState: MultiDeviceFeatureState):
      void {
    setPageContentData({
      ...multidevicePage.pageContentData,
      phoneHubNotificationsState: newState,
    });
  }

  function setPhoneHubNotificationAccessGranted(accessGranted: boolean): void {
    const accessState = accessGranted ?
        PhoneHubFeatureAccessStatus.ACCESS_GRANTED :
        PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED;
    setPageContentData({
      ...multidevicePage.pageContentData,
      notificationAccessStatus: accessState,
    });
  }

  function setNearbyShareIsOnboardingComplete(isOnboardingComplete: boolean):
      void {
    multidevicePage.setPrefValue(
        'nearby_sharing.onboarding_complete', isOnboardingComplete);
    flush();
  }

  function setNearbyShareEnabled(enabled: boolean): void {
    multidevicePage.setPrefValue('nearby_sharing.enabled', enabled);
    flush();
  }

  function setNearbyShareDisallowedByPolicy(isDisallowedByPolicy: boolean):
      void {
    setPageContentData({
      ...multidevicePage.pageContentData,
      isNearbyShareDisallowedByPolicy: isDisallowedByPolicy,
    });
  }

  function setPhoneHubPermissionsDialogSupported(enabled: boolean): void {
    setPageContentData({
      ...multidevicePage.pageContentData,
      isPhoneHubPermissionsDialogSupported: enabled,
    });
  }

  function getNearbyShareDisabledDescription(): string {
    // If the page is first with NS enabled, then there will be two
    // elements with id nearbyShareSecondary: in this case, the second will have
    // the off description. There should always be at least one element present
    // with that id.
    const nearbyShareSecondaryDisabledList =
        multidevicePage.shadowRoot!.querySelectorAll('#nearbyShareSecondary');
    const nearbyShareSecondaryDisabled =
        nearbyShareSecondaryDisabledList[nearbyShareSecondaryDisabledList.length - 1];
    assertTrue(!!nearbyShareSecondaryDisabled);

    const disabledLocalizedLink =
        nearbyShareSecondaryDisabled.querySelector('localized-link');
    assertTrue(!!disabledLocalizedLink);

    const disabledDescription =
        disabledLocalizedLink.shadowRoot!.querySelector<HTMLElement>(
            '#container');
    assertTrue(!!disabledDescription);
    return disabledDescription.innerText.trim();
  }

  /**
   * @param feature The feature to change.
   * @param enabled Whether to enable or disable the feature.
   * @param authRequired Whether authentication is required for the
   *     change.
   * @return Promise which resolves when the state change has been
   *     verified.
   */
  async function simulateFeatureStateChangeRequest(
      feature: MultiDeviceFeature, enabled: boolean,
      authRequired: boolean): Promise<void> {
    // When the user requests a feature state change, an event with the relevant
    // details is handled.
    multidevicePage.dispatchEvent(new CustomEvent('feature-toggle-clicked', {
      bubbles: true,
      composed: true,
      detail: {feature, enabled},
    }));
    flush();

    if (authRequired) {
      assertTrue(multidevicePage.get('showPasswordPromptDialog_'));
      const prompt = multidevicePage.shadowRoot!.querySelector(
          '#multidevicePasswordPrompt');
      assertTrue(!!prompt);
      // Simulate the user entering a valid password, then closing the dialog.
      prompt.dispatchEvent(new CustomEvent('token-obtained', {
        bubbles: true,
        composed: true,
        detail: {token: 'validAuthToken', lifetimeSeconds: 300},
      }));
      // Simulate closing the password prompt dialog
      prompt.dispatchEvent(
          new CustomEvent('close', {bubbles: true, composed: true}));
      flush();
    }

    if (enabled && feature === MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS) {
      const accessDialog =
          multidevicePage.pageContentData.isPhoneHubPermissionsDialogSupported ?
          multidevicePage.shadowRoot!.querySelector(
              'settings-multidevice-permissions-setup-dialog') :
          multidevicePage.shadowRoot!.querySelector(
              'settings-multidevice-notification-access-setup-dialog');
      assertEquals(
          !!accessDialog,
          multidevicePage.pageContentData.notificationAccessStatus ===
              PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED);
      return;
    }

    assertFalse(multidevicePage.get('showPasswordPromptDialog_'));
    const params = await browserProxy.whenCalled('setFeatureEnabledState');
    assertEquals(feature, params[0]);
    assertEquals(enabled, params[1]);
    // Reset the resolver so that setFeatureEnabledState() can be called
    // multiple times in a test.
    browserProxy.resetResolver('setFeatureEnabledState');
  }

  /**
   * Sets up Quick Share v2 tests which require the QuickShareV2 flag to be
   * enabled on page load.
   */
  async function setupQuickShareV2() {
    multidevicePage.remove();
    loadTimeData.overrideValues({'isQuickShareV2Enabled': true});
    await init();
  }

  suiteSetup(() => {
    ALL_MODES = Object.values(MultiDeviceSettingsMode)
                    .filter((item) => typeof item === 'number') as
        MultiDeviceSettingsMode[];

    fakeContactManager = new FakeContactManager();
    setContactManagerForTesting(fakeContactManager);
    fakeContactManager.setupContactRecords();

    fakeSettings = new FakeNearbyShareSettings();
    fakeSettings.setEnabled(true);
    setNearbyShareSettingsForTesting(fakeSettings);

    browserProxy = new TestMultideviceBrowserProxy();
    MultiDeviceBrowserProxyImpl.setInstanceForTesting(browserProxy);
  });

  async function init(): Promise<void> {
    multidevicePage = document.createElement('settings-multidevice-page');
    multidevicePage.prefs = {
      'nearby_sharing': {
        'onboarding_complete': {
          value: false,
        },
        'enabled': {
          value: false,
        },
      },
    };

    document.body.appendChild(multidevicePage);
    flush();
    await browserProxy.whenCalled('getPageContentData');
  }

  setup(async () => {
    loadTimeData.overrideValues({
      isNearbyShareSupported: true,
      isChromeosScreenLockEnabled: false,
      isPhoneScreenLockEnabled: false,
      // TODO(b/350547931): Permanently enable QSv2, remove flag and need to
      // override it.
      isQuickShareV2Enabled: false,
    });
    await init();
  });

  teardown(() => {
    multidevicePage.remove();
    browserProxy.reset();
    Router.getInstance().resetRouteForTesting();
  });

  function getLabel(): string {
    const element = multidevicePage.shadowRoot!.querySelector<HTMLElement>(
        '#multideviceLabel');
    assertTrue(!!element);
    return element.innerText.trim();
  }

  function getSublabel(): string {
    const element =
        multidevicePage.shadowRoot!.querySelector('#multideviceSubLabel')!
            .shadowRoot!.querySelector<HTMLElement>('#container');
    assertTrue(!!element);
    return element.innerText.trim();
  }

  function getSubpage(): SettingsMultideviceSubpageElement|null {
    return multidevicePage.shadowRoot!.querySelector(
        'settings-multidevice-subpage');
  }

  function getSubpageWrapper(): HTMLElement|null {
    return multidevicePage.shadowRoot!.querySelector<HTMLElement>(
        '#settingsMultideviceSubpageWrapper');
  }

  function getNearbyShareSecondary(): HTMLElement {
    const nearbyShareSecondary =
        multidevicePage.shadowRoot!.querySelector<HTMLElement>(
            '#nearbyShareSecondary');
    assertTrue(!!nearbyShareSecondary);
    return nearbyShareSecondary;
  }

  suite('nearby share description updates with isRevampWayfindingEnabled enabled', () => {
    setup(async () => {
      loadTimeData.overrideValues(
          {isNearbyShareSupported: true, isRevampWayfindingEnabled: true});

      await init();

      setNearbyShareDisallowedByPolicy(false);
      setNearbyShareIsOnboardingComplete(true);
      await flushTasks();
    });

    test(
        'nearby share off description shows when nearby share is disabled',
        () => {
          setNearbyShareEnabled(false);
          flush();

          const disabledDescription = getNearbyShareDisabledDescription();
          assertEquals(
              'Share files and more with nearby devices. Learn more',
              disabledDescription);
        });

    test(
        'nearby share visible to all description shows when visible to all contacts is selected',
        async () => {
          setNearbyShareEnabled(true);
          fakeSettings.setVisibility(Visibility.kAllContacts);
          await flushTasks();

          const nearbyShareSecondary = getNearbyShareSecondary();
          assertEquals(
              'Visible to all contacts', nearbyShareSecondary.innerText.trim());
        });

    test(
        'nearby share visible to all description shows when it is only visible to selected contacts',
        async () => {
          setNearbyShareEnabled(true);
          fakeSettings.setVisibility(Visibility.kSelectedContacts);
          await flushTasks();

          const nearbyShareSecondary = getNearbyShareSecondary();
          assertTrue(!!nearbyShareSecondary);
          assertEquals(
              'Visible to some contacts',
              nearbyShareSecondary.innerText.trim());
        });

    test(
        'nearby share visible to your devices shows when it is only visible to your devices',
        async () => {
          setNearbyShareEnabled(true);
          fakeSettings.setVisibility(Visibility.kYourDevices);
          await flushTasks();

          const nearbyShareSecondary = getNearbyShareSecondary();
          assertTrue(!!nearbyShareSecondary);
          assertEquals(
              'Visible to your devices', nearbyShareSecondary.innerText.trim());
        });

    test(
        'nearby share hidden description shows when no contact is selected',
        async () => {
          setNearbyShareEnabled(true);
          fakeSettings.setVisibility(Visibility.kNoOne);
          await flushTasks();

          const nearbyShareSecondary = getNearbyShareSecondary();
          assertTrue(!!nearbyShareSecondary);
          assertEquals('Hidden', nearbyShareSecondary.innerText.trim());
        });

    test(
        'nearby share description updates on visibility or enable states change',
        async () => {
          setNearbyShareEnabled(true);
          fakeSettings.setVisibility(Visibility.kNoOne);
          await flushTasks();

          const nearbyShareSecondaryEnabled = getNearbyShareSecondary();
          assertTrue(!!nearbyShareSecondaryEnabled);
          assertEquals('Hidden', nearbyShareSecondaryEnabled.innerText.trim());

          fakeSettings.setVisibility(Visibility.kAllContacts);
          await flushTasks();
          assertEquals(
              'Visible to all contacts',
              nearbyShareSecondaryEnabled.innerText.trim());

          fakeSettings.setVisibility(Visibility.kYourDevices);
          await flushTasks();
          assertEquals(
              'Visible to your devices',
              nearbyShareSecondaryEnabled.innerText.trim());

          setNearbyShareEnabled(false);
          flush();
          const disabledDescription = getNearbyShareDisabledDescription();
          assertEquals(
              'Share files and more with nearby devices. Learn more',
              disabledDescription);

          setNearbyShareEnabled(true);
          flush();
          fakeSettings.setVisibility(Visibility.kSelectedContacts);
          await flushTasks();
          assertEquals(
              'Visible to some contacts',
              nearbyShareSecondaryEnabled.innerText.trim());
        });
  });

  test('clicking setup shows multidevice setup dialog', async () => {
    setHostData(MultiDeviceSettingsMode.NO_HOST_SET);
    const button = multidevicePage.shadowRoot!.querySelector('cr-button');
    assertTrue(!!button);
    button.click();
    await browserProxy.whenCalled('showMultiDeviceSetupDialog');
  });

  test('Deep link to multidevice setup', async () => {
    setHostData(MultiDeviceSettingsMode.NO_HOST_SET);

    const SETTING_ID_200 = settingMojom.Setting.kSetUpMultiDevice.toString();
    const params = new URLSearchParams();
    params.append('settingId', SETTING_ID_200);
    Router.getInstance().navigateTo(routes.MULTIDEVICE, params);

    flush();

    const deepLinkElement =
        multidevicePage.shadowRoot!.querySelector('cr-button');
    assertTrue(!!deepLinkElement);
    await waitAfterNextRender(deepLinkElement);
    assertEquals(
        deepLinkElement, getDeepActiveElement(),
        `Setup multidevice button should be focused for settingId=${
            SETTING_ID_200}.`);
  });

  test('Open notification access setup dialog route param', async () => {
    Router.getInstance().navigateTo(
        routes.MULTIDEVICE_FEATURES,
        new URLSearchParams('showPhonePermissionSetupDialog=true'));

    browserProxy.setNotificationAccessStatusForTesting(
        PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED);

    await init();

    const dialog = multidevicePage.shadowRoot!.querySelector(
        'settings-multidevice-notification-access-setup-dialog');
    assertTrue(!!dialog);

    // Close the dialog.
    dialog.$.dialog.close();
    await flushTasks();

    // Check the subpage is focused on dialog close.
    assertEquals(
        getSubpageWrapper(), getDeepActiveElement(),
        'subpage wrapper should be focused.');

    // A change in pageContentData will not cause the notification access
    // setup dialog to reappear
    setHostData(MultiDeviceSettingsMode.NO_HOST_SET);
    flush();

    assertNull(multidevicePage.shadowRoot!.querySelector(
        'settings-multidevice-notification-access-setup-dialog'));
  });

  test('Open multidevice permissions setup dialog route param', async () => {
    Router.getInstance().navigateTo(
        routes.MULTIDEVICE_FEATURES,
        new URLSearchParams('showPhonePermissionSetupDialog&mode=1'));

    browserProxy.setNotificationAccessStatusForTesting(
        PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED);
    browserProxy.setIsPhoneHubPermissionsDialogSupportedForTesting(true);

    await init();

    assertTrue(!!multidevicePage.shadowRoot!.querySelector(
        'settings-multidevice-permissions-setup-dialog'));

    // Close the dialog.
    multidevicePage.set('showPhonePermissionSetupDialog_', false);
    flush();

    // A change in pageContentData will not cause the multidevice permissions
    // setup dialog to reappear.
    setHostData(MultiDeviceSettingsMode.NO_HOST_SET);
    flush();

    assertNull(multidevicePage.shadowRoot!.querySelector(
        'settings-multidevice-permissions-setup-dialog'));
  });

  if (isRevampWayfindingEnabled) {
    test('Label always shows "Android phone" for all modes', () => {
      for (const mode of ALL_MODES) {
        setHostData(mode);
        assertEquals('Android phone', getLabel());
      }
    });
  } else {
    test('Label changes based on mode and host', () => {
      for (const mode of ALL_MODES) {
        setHostData(mode);
        assertEquals(multidevicePage.isHostSet(), getLabel() === HOST_DEVICE);
      }
    });
  }

  if (isRevampWayfindingEnabled) {
    test('Host device name displayed updates if the device is changed', () => {
      setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED);
      assertEquals('Android phone', getLabel());
      assertEquals(HOST_DEVICE, getSublabel());

      const anotherHost = `Super Duper ${HOST_DEVICE}`;
      setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED, anotherHost);
      assertEquals('Android phone', getLabel());
      assertEquals(anotherHost, getSublabel());
    });

    test('Labels for no eligible host device', () => {
      setHostData(MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS);
      assertEquals('Android phone', getLabel());
      assertEquals(
          'No available devices. Add your Google Account to your phone to ' +
              'connect it to this Chrome device. Learn more',
          getSublabel());
    });
  } else {
    test('changing host device changes label', () => {
      setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED);
      assertEquals(HOST_DEVICE, getLabel());

      const anotherHost = `Super Duper ${HOST_DEVICE}`;
      setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED, anotherHost);
      assertEquals(anotherHost, getLabel());
    });

    test('Labels for no eligible host device', () => {
      setHostData(MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS);
      assertEquals('Android phone', getLabel());
      assertEquals('No eligible devices. Learn more', getSublabel());
    });
  }

  test('item is actionable if and only if a host is set', () => {
    for (const mode of ALL_MODES) {
      setHostData(mode);
      const suiteLinkWrapper =
          multidevicePage.shadowRoot!.querySelector('#suiteLinkWrapper');
      assertTrue(!!suiteLinkWrapper);
      assertEquals(
          multidevicePage.isHostSet(),
          suiteLinkWrapper.hasAttribute('actionable'));
    }
  });

  test('clicking item with verified host opens subpage with features', () => {
    setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED);
    assertNull(getSubpage());
    const suiteLinkWrapper =
        multidevicePage.shadowRoot!.querySelector<HTMLElement>(
            '#suiteLinkWrapper');
    assertTrue(!!suiteLinkWrapper);
    suiteLinkWrapper.click();
    assertTrue(!!getSubpage());
    assertTrue(!!getSubpage()!.shadowRoot!.querySelector(
        'settings-multidevice-feature-item'));
  });

  test(
      'clicking item with unverified set host opens subpage without features',
      () => {
        setHostData(
            MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
            HOST_DEVICE);
        assertNull(getSubpage());
        const suiteLinkWrapper =
            multidevicePage.shadowRoot!.querySelector<HTMLElement>(
                '#suiteLinkWrapper');
        assertTrue(!!suiteLinkWrapper);
        suiteLinkWrapper.click();
        assertTrue(!!getSubpage());
        assertNull(getSubpage()!.shadowRoot!.querySelector(
            'settings-multidevice-feature-item'));
      });

  test(
      'Multidevice subpage trigger should be focused after returning from ' +
          'subpage',
      async () => {
        Router.getInstance().navigateTo(routes.MULTIDEVICE);
        setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED);

        // Sub-page trigger navigates to Multidevice Features subpage
        const triggerSelector = '#multideviceItem .subpage-arrow';
        const subpageTrigger =
            multidevicePage.shadowRoot!.querySelector<HTMLButtonElement>(
                triggerSelector);
        assertTrue(!!subpageTrigger);
        subpageTrigger.click();
        assertEquals(
            routes.MULTIDEVICE_FEATURES, Router.getInstance().currentRoute);

        // Navigate back
        const popStateEventPromise = eventToPromise('popstate', window);
        Router.getInstance().navigateToPreviousRoute();
        await popStateEventPromise;
        await waitAfterNextRender(multidevicePage);

        assertEquals(
            subpageTrigger, multidevicePage.shadowRoot!.activeElement,
            `${triggerSelector} should be focused.`);
      });

  test('policy prohibited suite shows policy indicator', () => {
    setHostData(MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS);
    assertNull(
        multidevicePage.shadowRoot!.querySelector('#suitePolicyIndicator'));
    // Prohibit suite by policy.
    setSuiteState(MultiDeviceFeatureState.PROHIBITED_BY_POLICY);
    assertTrue(
        !!multidevicePage.shadowRoot!.querySelector('#suitePolicyIndicator'));
    // Reallow suite.
    setSuiteState(MultiDeviceFeatureState.DISABLED_BY_USER);
    assertNull(
        multidevicePage.shadowRoot!.querySelector('#suitePolicyIndicator'));
  });

  test('Multidevice permissions setup dialog', () => {
    setPhoneHubNotificationsState(MultiDeviceFeatureState.DISABLED_BY_USER);
    assertNull(multidevicePage.shadowRoot!.querySelector(
        'settings-multidevice-notification-access-setup-dialog'));

    setPhoneHubNotificationAccessGranted(false);
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ true, /*authRequired=*/ false);

    // Close the dialog.
    multidevicePage.set('showPhonePermissionSetupDialog_', false);

    setPhoneHubNotificationAccessGranted(false);
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ false, /*authRequired=*/ false);

    setPhoneHubNotificationAccessGranted(true);
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ true, /*authRequired=*/ false);

    multidevicePage.pageContentData.notificationAccessStatus =
        PhoneHubFeatureAccessStatus.ACCESS_GRANTED;
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ false, /*authRequired=*/ false);
  });

  test('New multidevice permissions setup dialog', () => {
    setPhoneHubPermissionsDialogSupported(true);
    setPhoneHubNotificationsState(MultiDeviceFeatureState.DISABLED_BY_USER);
    assertNull(multidevicePage.shadowRoot!.querySelector(
        'settings-multidevice-permissions-setup-dialog'));

    setPhoneHubNotificationAccessGranted(false);
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ true, /*authRequired=*/ false);

    // Close the dialog.
    multidevicePage.set('showPhonePermissionSetupDialog_', false);

    setPhoneHubNotificationAccessGranted(false);
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ false, /*authRequired=*/ false);

    setPhoneHubNotificationAccessGranted(true);
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ true, /*authRequired=*/ false);

    multidevicePage.pageContentData.notificationAccessStatus =
        PhoneHubFeatureAccessStatus.ACCESS_GRANTED;
    simulateFeatureStateChangeRequest(
        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
        /*enabled=*/ false, /*authRequired=*/ false);
  });

  test('Disabling features never requires authentication', async () => {
    const Feature = MultiDeviceFeature;

    const disableFeatureFn = (feature: MultiDeviceFeature) =>
        simulateFeatureStateChangeRequest(
            feature, false /* enabled */, false /* authRequired */);

    await disableFeatureFn(Feature.BETTER_TOGETHER_SUITE);
    await disableFeatureFn(Feature.INSTANT_TETHERING);
    await disableFeatureFn(Feature.SMART_LOCK);
  });

  test(
      'Enabling some features requires authentication; others do not',
      async () => {
        const Feature = MultiDeviceFeature;
        const FeatureState = MultiDeviceFeatureState;

        const enableFeatureWithoutAuthFn = (feature: MultiDeviceFeature) =>
            simulateFeatureStateChangeRequest(
                feature, true /* enabled */, false /* authRequired */);

        const enableFeatureWithAuthFn = (feature: MultiDeviceFeature) =>
            simulateFeatureStateChangeRequest(
                feature, true /* enabled */, true /* authRequired */);

        // Start out with SmartLock being disabled by the user. This means that
        // the first attempt to enable BETTER_TOGETHER_SUITE below will not
        // require authentication.
        setSmartLockState(FeatureState.DISABLED_BY_USER);

        // INSTANT_TETHERING requires no authentication.
        await enableFeatureWithoutAuthFn(Feature.INSTANT_TETHERING);
        // BETTER_TOGETHER_SUITE requires no authentication normally.
        await enableFeatureWithoutAuthFn(Feature.BETTER_TOGETHER_SUITE);
        // BETTER_TOGETHER_SUITE requires authentication when SmartLock's
        // state is UNAVAILABLE_SUITE_DISABLED.
        setSmartLockState(FeatureState.UNAVAILABLE_SUITE_DISABLED);
        await enableFeatureWithAuthFn(Feature.BETTER_TOGETHER_SUITE);
        // BETTER_TOGETHER_SUITE requires authentication when SmartLock's
        // state is UNAVAILABLE_INSUFFICIENT_SECURITY.
        setSmartLockState(FeatureState.UNAVAILABLE_INSUFFICIENT_SECURITY);
        await enableFeatureWithAuthFn(Feature.BETTER_TOGETHER_SUITE);
        // SMART_LOCK always requires authentication.
        await enableFeatureWithAuthFn(Feature.SMART_LOCK);
      });

  test('Nearby setup button shown before onboarding is complete', () => {
    setNearbyShareDisallowedByPolicy(false);
    assertTrue(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));
    assertFalse(isVisible(multidevicePage.shadowRoot!.querySelector(
        '#nearbySharingToggleButton')));

    setNearbyShareIsOnboardingComplete(true);
    assertFalse(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));

    const nearbySharingToggleButton =
        multidevicePage.shadowRoot!.querySelector<HTMLButtonElement>(
            '#nearbySharingToggleButton');
    assertTrue(!!nearbySharingToggleButton);
    assertFalse(nearbySharingToggleButton.disabled);
  });

  test('Nearby disabled toggle shown if disallowed by policy', () => {
    setNearbyShareDisallowedByPolicy(false);
    assertTrue(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));
    assertFalse(isVisible(multidevicePage.shadowRoot!.querySelector(
        '#nearbySharingToggleButton')));

    setNearbyShareDisallowedByPolicy(true);
    assertFalse(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));

    const nearbySharingToggleButton =
        multidevicePage.shadowRoot!.querySelector<HTMLButtonElement>(
            '#nearbySharingToggleButton');
    assertTrue(!!nearbySharingToggleButton);
    assertTrue(nearbySharingToggleButton.disabled);
  });

  test('Nearby description shown before onboarding is completed', () => {
    setNearbyShareDisallowedByPolicy(false);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#setupDescription')));

    setNearbyShareIsOnboardingComplete(true);
    assertFalse(isVisible(
        multidevicePage.shadowRoot!.querySelector('#setupDescription')));

    const disabledDescription = getNearbyShareDisabledDescription();
    const expectedText = 'Share files and more with nearby devices. Learn more';
    assertEquals(expectedText, disabledDescription);
  });

  test('Nearby description shown if disallowed by policy', () => {
    setNearbyShareDisallowedByPolicy(false);
    setNearbyShareIsOnboardingComplete(true);
    assertFalse(isVisible(
        multidevicePage.shadowRoot!.querySelector('#setupDescription')));

    const disabledDescription = getNearbyShareDisabledDescription();

    const expectedText = 'Share files and more with nearby devices. Learn more';
    assertEquals(expectedText, disabledDescription);

    setNearbyShareDisallowedByPolicy(true);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#setupDescription')));
  });

  test('Nearby policy indicator shown when disallowed by policy', () => {
    setNearbyShareDisallowedByPolicy(false);
    assertFalse(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyPolicyIndicator')));

    setNearbyShareDisallowedByPolicy(true);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyPolicyIndicator')));

    setNearbyShareDisallowedByPolicy(false);
    assertFalse(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyPolicyIndicator')));
  });

  test('Nearby subpage not available when disallowed by policy', () => {
    setNearbyShareDisallowedByPolicy(true);
    let nearbyLinkWrapper =
        multidevicePage.shadowRoot!.querySelector('#nearbyLinkWrapper');
    assertTrue(!!nearbyLinkWrapper);
    assertFalse(nearbyLinkWrapper.hasAttribute('actionable'));

    setNearbyShareDisallowedByPolicy(false);
    nearbyLinkWrapper =
        multidevicePage.shadowRoot!.querySelector('#nearbyLinkWrapper');
    assertTrue(!!nearbyLinkWrapper);
    assertTrue(nearbyLinkWrapper.hasAttribute('actionable'));
  });

  test('Better Together Suite icon visible when there is no host set', () => {
    setHostData(MultiDeviceSettingsMode.NO_HOST_SET);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#betterTogetherSuiteIcon')));
  });

  test('Better Together Suite icon visible when there is a host set', () => {
    setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#betterTogetherSuiteIcon')));
  });

  test('Better Together Suite icon remains visible when host added', () => {
    setHostData(MultiDeviceSettingsMode.NO_HOST_SET);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#betterTogetherSuiteIcon')));

    setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#betterTogetherSuiteIcon')));
  });

  test('Better Together Suite icon remains visible when host removed', () => {
    setHostData(MultiDeviceSettingsMode.HOST_SET_VERIFIED);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#betterTogetherSuiteIcon')));

    setHostData(MultiDeviceSettingsMode.NO_HOST_SET);
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#betterTogetherSuiteIcon')));
  });

  test('Nearby share sub page arrow is not visible before onboarding', () => {
    setNearbyShareDisallowedByPolicy(false);
    assertTrue(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyShareSubpageArrow')));

    setNearbyShareIsOnboardingComplete(true);
    setNearbyShareEnabled(true);
    flush();
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyShareSubpageArrow')));
  });

  test(
      'Clicking nearby subpage before onboarding initiates onboarding',
      async () => {
        setNearbyShareDisallowedByPolicy(false);
        assertTrue(isVisible(
            multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));
        assertTrue(isVisible(multidevicePage.shadowRoot!.querySelector(
            '#nearbyShareSubpageArrow')));

        const router = Router.getInstance();
        const nearbyLinkWrapper =
            multidevicePage.shadowRoot!.querySelector<HTMLElement>(
                '#nearbyLinkWrapper');
        assertTrue(!!nearbyLinkWrapper);
        nearbyLinkWrapper.click();
        await flushTasks();
        assertEquals(routes.NEARBY_SHARE, router.currentRoute);
        assertFalse(router.getQueryParameters().has('onboarding'));
      });

  test('Clicking nearby subpage after onboarding enters subpage', async () => {
    setNearbyShareDisallowedByPolicy(false);
    assertTrue(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyShareSubpageArrow')));

    setNearbyShareIsOnboardingComplete(true);
    setNearbyShareEnabled(true);
    flush();

    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyShareSubpageArrow')));
    const router = Router.getInstance();
    const nearbyLinkWrapper =
        multidevicePage.shadowRoot!.querySelector<HTMLElement>(
            '#nearbyLinkWrapper');
    assertTrue(!!nearbyLinkWrapper);
    nearbyLinkWrapper.click();
    await flushTasks();
    assertEquals(routes.NEARBY_SHARE, router.currentRoute);
    assertFalse(router.getQueryParameters().has('onboarding'));
  });

  test('Settings mojo changes propagate to settings property', async () => {
    // Allow initial settings to be loaded.
    await flushTasks();

    const newName = 'NEW NAME';
    assertNotEquals(newName, multidevicePage.get('settings.deviceName'));

    await fakeSettings.setDeviceName(newName);
    await flushTasks();
    assertEquals(newName, multidevicePage.get('settings.deviceName'));

    const newEnabledState = !multidevicePage.get('settings.enabled');
    assertNotEquals(newEnabledState, multidevicePage.get('settings.enabled'));

    await fakeSettings.setEnabled(newEnabledState);
    await flushTasks();
    assertEquals(newEnabledState, multidevicePage.get('settings.enabled'));
  });

  test('Screen lock changes propagate to settings property', () => {
    setScreenLockStatus(/* chromeStatus= */ true, /* phoneStatus= */ true);

    assertTrue(multidevicePage.get('isChromeosScreenLockEnabled_'));
    assertTrue(multidevicePage.get('isPhoneScreenLockEnabled_'));
  });

  test('Nearby share sub page arrow is visible before onboarding', async () => {
    // Arrow only visible if background scanning feature flag is enabled
    // and hardware offloading is supported.
    await flushTasks();
    setNearbyShareDisallowedByPolicy(false);
    multidevicePage.set('settings.isFastInitiationHardwareSupported', true);

    setNearbyShareDisallowedByPolicy(false);
    assertTrue(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyShareSubpageArrow')));

    setNearbyShareIsOnboardingComplete(true);
    setNearbyShareEnabled(true);
    await flushTasks();
    assertTrue(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyShareSubpageArrow')));
  });

  test('No Background Scanning hardware support', async () => {
    // Ensure initial nearby settings values are set before overriding.
    await flushTasks();
    setNearbyShareDisallowedByPolicy(false);
    multidevicePage.set('settings.isFastInitiationHardwareSupported', false);
    await flushTasks();

    assertTrue(
        isVisible(multidevicePage.shadowRoot!.querySelector('#nearbySetUp')));
    assertFalse(isVisible(
        multidevicePage.shadowRoot!.querySelector('#nearbyShareSubpageArrow')));

    // Clicking on Nearby Subpage row should initiate onboarding.
    const router = Router.getInstance();
    const nearbyLinkWrapper =
        multidevicePage.shadowRoot!.querySelector<HTMLElement>(
            '#nearbyLinkWrapper');
    assertTrue(!!nearbyLinkWrapper);
    nearbyLinkWrapper.click();
    await flushTasks();
    assertEquals(routes.NEARBY_SHARE, router.currentRoute);
    assertTrue(router.getQueryParameters().has('onboarding'));
  });

  test('Clicking nearby subpage before onboarding enters subpage', async () => {
    setNearbyShareDisallowedByPolicy(false);
    await flushTasks();

    const router = Router.getInstance();
    const nearbyLinkWrapper =
        multidevicePage.shadowRoot!.querySelector<HTMLElement>(
            '#nearbyLinkWrapper');
    assertTrue(!!nearbyLinkWrapper);
    nearbyLinkWrapper.click();

    await flushTasks();
    assertEquals(routes.NEARBY_SHARE, router.currentRoute);
    assertFalse(router.getQueryParameters().has('onboarding'));
  });

  test(
      'Subpage shows no Quick Share on/off toggle on QuickShareV2 enabled',
      async () => {
        setupQuickShareV2();
        const quickShareToggle = multidevicePage.shadowRoot!.querySelector(
            '#nearbySharingToggleButton');
        assertFalse(!!quickShareToggle);
      });
});