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

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

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

import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import type {CrExpandButtonElement, CrInputElement, SettingsSyncEncryptionOptionsElement, SettingsSyncPageElement} from 'chrome://settings/lazy_load.js';
// <if expr="not chromeos_ash">
import type {CrDialogElement} from 'chrome://settings/lazy_load.js';
// </if>
import type {CrCollapseElement} from 'chrome://settings/lazy_load.js';
import type {CrButtonElement, CrRadioButtonElement, CrRadioGroupElement} from 'chrome://settings/settings.js';
import {MetricsBrowserProxyImpl} from 'chrome://settings/settings.js';
import {OpenWindowProxyImpl, PageStatus, Router, routes, SignedInState, StatusAction, SyncBrowserProxyImpl} from 'chrome://settings/settings.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks, waitBeforeNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {TestOpenWindowProxy} from 'chrome://webui-test/test_open_window_proxy.js';
import {isChildVisible, microtasksFinished, eventToPromise} from 'chrome://webui-test/test_util.js';

// <if expr="not chromeos_ash">
import {simulateStoredAccounts} from './sync_test_util.js';
// </if>

import {getSyncAllPrefs} from './sync_test_util.js';
import {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
import {TestSyncBrowserProxy} from './test_sync_browser_proxy.js';

// clang-format on

suite('SyncSettings', function() {
  let syncPage: SettingsSyncPageElement;
  let browserProxy: TestSyncBrowserProxy;
  let encryptionElement: SettingsSyncEncryptionOptionsElement;
  let encryptionRadioGroup: CrRadioGroupElement;
  let encryptWithGoogle: CrRadioButtonElement;
  let encryptWithPassphrase: CrRadioButtonElement;

  function setupSyncPage() {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    syncPage = document.createElement('settings-sync-page');
    const router = Router.getInstance();
    router.navigateTo(router.getRoutes().SYNC);
    // Preferences should exist for embedded
    // 'personalization_options.html'. We don't perform tests on them.
    syncPage.prefs = {
      profile: {password_manager_leak_detection: {value: true}},
      signin: {
        allowed_on_next_startup:
            {type: chrome.settingsPrivate.PrefType.BOOLEAN, value: true},
      },
      safebrowsing:
          {enabled: {value: true}, scout_reporting_enabled: {value: true}},
    };

    document.body.appendChild(syncPage);

    webUIListenerCallback('page-status-changed', PageStatus.CONFIGURE);
    assertFalse(
        syncPage.shadowRoot!
            .querySelector<HTMLElement>('#' + PageStatus.CONFIGURE)!.hidden);
    assertTrue(
        syncPage.shadowRoot!
            .querySelector<HTMLElement>('#' + PageStatus.SPINNER)!.hidden);

    // Start with Sync All with no encryption selected. Also, ensure
    // that this is not a supervised user, so that Sync Passphrase is
    // enabled.
    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefs());
    syncPage.set('syncStatus', {
      signedInState: SignedInState.SYNCING,
      supervisedUser: false,
      statusAction: StatusAction.NO_ACTION,
    });
    flush();
  }

  suiteSetup(function() {
    loadTimeData.overrideValues({signinAllowed: true});
  });

  setup(async function() {
    browserProxy = new TestSyncBrowserProxy();
    SyncBrowserProxyImpl.setInstance(browserProxy);

    setupSyncPage();

    await waitBeforeNextRender(syncPage);
    encryptionElement =
        syncPage.shadowRoot!.querySelector('settings-sync-encryption-options')!;
    assertTrue(!!encryptionElement);
    encryptionRadioGroup =
        encryptionElement.shadowRoot!.querySelector('#encryptionRadioGroup')!;
    encryptWithGoogle = encryptionElement.shadowRoot!.querySelector(
        'cr-radio-button[name="encrypt-with-google"]')!;
    encryptWithPassphrase = encryptionElement.shadowRoot!.querySelector(
        'cr-radio-button[name="encrypt-with-passphrase"]')!;
    assertTrue(!!encryptionRadioGroup);
    assertTrue(!!encryptWithGoogle);
    assertTrue(!!encryptWithPassphrase);
  });

  teardown(function() {
    syncPage.remove();
  });

  // #######################
  // TESTS FOR ALL PLATFORMS
  // #######################

  test('NotifiesHandlerOfNavigation', async function() {
    await browserProxy.whenCalled('didNavigateToSyncPage');

    // Navigate away.
    const router = Router.getInstance();
    router.navigateTo(router.getRoutes().PEOPLE);
    await browserProxy.whenCalled('didNavigateAwayFromSyncPage');

    // Navigate back to the page.
    browserProxy.resetResolver('didNavigateToSyncPage');
    router.navigateTo(router.getRoutes().SYNC);
    await browserProxy.whenCalled('didNavigateToSyncPage');

    // Remove page element.
    browserProxy.resetResolver('didNavigateAwayFromSyncPage');
    syncPage.remove();
    await browserProxy.whenCalled('didNavigateAwayFromSyncPage');

    // Recreate page element.
    browserProxy.resetResolver('didNavigateToSyncPage');
    syncPage = document.createElement('settings-sync-page');
    router.navigateTo(router.getRoutes().SYNC);
    document.body.appendChild(syncPage);
    await browserProxy.whenCalled('didNavigateToSyncPage');
  });

  test('SyncSectionLayout_SignedIn', function() {
    const syncSection =
        syncPage.shadowRoot!.querySelector<HTMLElement>('#sync-section')!;
    const otherItems =
        syncPage.shadowRoot!.querySelector<HTMLElement>('#other-sync-items')!;

    syncPage.syncStatus = {
      signedInState: SignedInState.SYNCING,
      disabled: false,
      hasError: false,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertFalse(syncSection.hidden);
    assertTrue(
        syncPage.shadowRoot!.querySelector<HTMLElement>(
                                '#sync-separator')!.hidden);
    assertTrue(otherItems.classList.contains('list-frame'));
    assertEquals(otherItems.querySelectorAll('cr-expand-button').length, 1);

    assertTrue(isChildVisible(syncPage, '#sync-advanced-row'));
    // <if expr="chromeos_lacros">
    assertTrue(isChildVisible(syncPage, '#os-sync-device-row'));
    // </if>
    // TODO(crbug.com/324091979): Remove once crbug.com/324091979 launched.
    assertFalse(isChildVisible(syncPage, '#activityControlsLinkRowV1'));

    assertTrue(isChildVisible(syncPage, '#activityControlsLinkRowV2'));
    assertFalse(isChildVisible(syncPage, '#personalizationExpandButton'));
    assertTrue(isChildVisible(syncPage, '#syncDashboardLink'));

    // Test sync paused state.
    syncPage.syncStatus = {
      signedInState: SignedInState.SYNCING,
      disabled: false,
      hasError: true,
      statusAction: StatusAction.REAUTHENTICATE,
    };
    assertTrue(syncSection.hidden);
    assertFalse(
        syncPage.shadowRoot!.querySelector<HTMLElement>(
                                '#sync-separator')!.hidden);

    // Test passphrase error state.
    syncPage.syncStatus = {
      signedInState: SignedInState.SYNCING,
      disabled: false,
      hasError: true,
      statusAction: StatusAction.ENTER_PASSPHRASE,
    };
    assertFalse(syncSection.hidden);
    assertTrue(
        syncPage.shadowRoot!.querySelector<HTMLElement>(
                                '#sync-separator')!.hidden);
  });

  test('SyncSectionLayout_SignedOut', function() {
    const syncSection =
        syncPage.shadowRoot!.querySelector<HTMLElement>('#sync-section')!;

    syncPage.syncStatus = {
      signedInState: SignedInState.SIGNED_OUT,
      disabled: false,
      hasError: false,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertTrue(syncSection.hidden);
    assertFalse(
        syncPage.shadowRoot!.querySelector<HTMLElement>(
                                '#sync-separator')!.hidden);
  });

  test('SyncSectionLayout_SyncDisabled', function() {
    const syncSection =
        syncPage.shadowRoot!.querySelector<HTMLElement>('#sync-section')!;

    syncPage.syncStatus = {
      signedInState: SignedInState.SIGNED_IN,
      disabled: true,
      hasError: false,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertTrue(syncSection.hidden);
  });

  test('LoadingAndTimeout', function() {
    const configurePage = syncPage.shadowRoot!.querySelector<HTMLElement>(
        '#' + PageStatus.CONFIGURE)!;
    const spinnerPage = syncPage.shadowRoot!.querySelector<HTMLElement>(
        '#' + PageStatus.SPINNER)!;

    // NOTE: This isn't called in production, but the test suite starts the
    // tests with PageStatus.CONFIGURE.
    webUIListenerCallback('page-status-changed', PageStatus.SPINNER);
    assertTrue(configurePage.hidden);
    assertFalse(spinnerPage.hidden);

    webUIListenerCallback('page-status-changed', PageStatus.CONFIGURE);
    assertFalse(configurePage.hidden);
    assertTrue(spinnerPage.hidden);

    // Should remain on the CONFIGURE page even if the passphrase failed.
    webUIListenerCallback('page-status-changed', PageStatus.PASSPHRASE_FAILED);
    assertFalse(configurePage.hidden);
    assertTrue(spinnerPage.hidden);
  });

  test('EncryptionExpandButton', async function() {
    const encryptionDescription =
        syncPage.shadowRoot!.querySelector<CrExpandButtonElement>(
            '#encryptionDescription');
    assertTrue(!!encryptionDescription);
    const encryptionCollapse = syncPage.$.encryptionCollapse;

    // No encryption with custom passphrase.
    assertFalse(encryptionCollapse.opened);
    encryptionDescription.click();
    await encryptionDescription.updateComplete;
    assertTrue(encryptionCollapse.opened);

    // Push sync prefs with |prefs.encryptAllData| unchanged. The encryption
    // menu should not collapse.
    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefs());
    flush();
    assertTrue(encryptionCollapse.opened);

    encryptionDescription.click();
    await encryptionDescription.updateComplete;
    assertFalse(encryptionCollapse.opened);

    // Data encrypted with custom passphrase.
    // The encryption menu should be expanded.
    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    webUIListenerCallback('sync-prefs-changed', prefs);
    flush();
    assertTrue(encryptionCollapse.opened);

    // Clicking |reset Sync| does not change the expansion state.
    const link =
        encryptionDescription.querySelector<HTMLAnchorElement>('a[href]');
    assertTrue(!!link);
    link!.target = '';
    link!.href = '#';
    // Prevent the link from triggering a page navigation when tapped.
    // Breaks the test in Vulcanized mode.
    link!.addEventListener('click', e => e.preventDefault());
    link!.click();
    assertTrue(encryptionCollapse.opened);
  });

  test('RadioBoxesEnabledWhenUnencrypted', async () => {
    // Verify that the encryption radio boxes are enabled.
    assertFalse(encryptionRadioGroup.disabled);
    assertEquals(encryptWithGoogle.getAttribute('aria-disabled'), 'false');
    assertEquals(encryptWithPassphrase.getAttribute('aria-disabled'), 'false');

    assertTrue(encryptWithGoogle.checked);

    // Select 'Encrypt with passphrase' to create a new passphrase.
    assertFalse(
        !!encryptionElement.shadowRoot!.querySelector('#create-password-box'));

    encryptWithPassphrase.click();
    await eventToPromise('selected-changed', encryptionRadioGroup);

    assertTrue(
        !!encryptionElement.shadowRoot!.querySelector('#create-password-box'));
    const saveNewPassphrase =
        encryptionElement.shadowRoot!.querySelector<CrButtonElement>(
            '#saveNewPassphrase');
    assertTrue(!!saveNewPassphrase);

    // Test that a sync prefs update does not reset the selection.
    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefs());
    // Wait for a timeout so that the check below catches any incorrect reset.
    await microtasksFinished();
    assertTrue(encryptWithPassphrase.checked);
  });

  test('ClickingLinkDoesNotChangeRadioValue', function() {
    assertFalse(encryptionRadioGroup.disabled);
    assertEquals(encryptWithPassphrase.getAttribute('aria-disabled'), 'false');
    assertFalse(encryptWithPassphrase.checked);

    const link =
        encryptWithPassphrase.querySelector<HTMLAnchorElement>('a[href]');
    assertTrue(!!link);

    // Suppress opening a new tab, since then the test will continue running
    // on a background tab (which has throttled timers) and will timeout.
    link!.target = '';
    link!.href = '#';
    // Prevent the link from triggering a page navigation when tapped.
    // Breaks the test in Vulcanized mode.
    link!.addEventListener('click', function(e) {
      e.preventDefault();
    });

    link!.click();

    assertFalse(encryptWithPassphrase.checked);
  });

  test('SaveButtonDisabledWhenPassphraseOrConfirmationEmpty', async () => {
    encryptWithPassphrase.click();
    await eventToPromise('selected-changed', encryptionRadioGroup);

    assertTrue(
        !!encryptionElement.shadowRoot!.querySelector('#create-password-box'));
    const saveNewPassphrase =
        encryptionElement.shadowRoot!.querySelector<CrButtonElement>(
            '#saveNewPassphrase')!;
    const passphraseInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseInput')!;
    const passphraseConfirmationInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseConfirmationInput')!;

    passphraseInput.value = '';
    passphraseConfirmationInput.value = '';
    await Promise.all([
      passphraseInput.updateComplete,
      passphraseConfirmationInput.updateComplete,
    ]);
    assertTrue(saveNewPassphrase.disabled);

    passphraseInput.value = 'foo';
    passphraseConfirmationInput.value = '';
    await Promise.all([
      passphraseInput.updateComplete,
      passphraseConfirmationInput.updateComplete,
    ]);
    assertTrue(saveNewPassphrase.disabled);

    passphraseInput.value = 'foo';
    passphraseConfirmationInput.value = 'bar';
    await Promise.all([
      passphraseInput.updateComplete,
      passphraseConfirmationInput.updateComplete,
    ]);
    assertFalse(saveNewPassphrase.disabled);
  });

  test('CreatingPassphraseMismatchedPassphrase', async () => {
    encryptWithPassphrase.click();
    await eventToPromise('selected-changed', encryptionRadioGroup);

    assertTrue(
        !!encryptionElement.shadowRoot!.querySelector('#create-password-box'));
    const saveNewPassphrase =
        encryptionElement.shadowRoot!.querySelector<CrButtonElement>(
            '#saveNewPassphrase');
    assertTrue(!!saveNewPassphrase);

    const passphraseInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseInput')!;
    const passphraseConfirmationInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseConfirmationInput')!;
    passphraseInput.value = 'foo';
    passphraseConfirmationInput.value = 'bar';
    await Promise.all([
      passphraseInput.updateComplete,
      passphraseConfirmationInput.updateComplete,
    ]);

    saveNewPassphrase!.click();
    flush();

    assertFalse(passphraseInput.invalid);
    assertTrue(passphraseConfirmationInput.invalid);
  });

  test('CreatingPassphraseValidPassphrase', async function() {
    encryptWithPassphrase.click();
    await eventToPromise('selected-changed', encryptionRadioGroup);

    assertTrue(
        !!encryptionElement.shadowRoot!.querySelector('#create-password-box'));
    const saveNewPassphrase =
        encryptionElement.shadowRoot!.querySelector<CrButtonElement>(
            '#saveNewPassphrase');
    assertTrue(!!saveNewPassphrase);

    const passphraseInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseInput')!;
    const passphraseConfirmationInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseConfirmationInput')!;
    passphraseInput.value = 'foo';
    passphraseConfirmationInput.value = 'foo';
    browserProxy.encryptionPassphraseSuccess = true;
    await Promise.all([
      passphraseInput.updateComplete,
      passphraseConfirmationInput.updateComplete,
    ]);
    saveNewPassphrase!.click();

    const passphrase = await browserProxy.whenCalled('setEncryptionPassphrase');

    assertEquals('foo', passphrase);

    // Fake backend response.
    const newPrefs = getSyncAllPrefs();
    newPrefs.encryptAllData = true;
    webUIListenerCallback('sync-prefs-changed', newPrefs);

    flush();

    await waitBeforeNextRender(syncPage);
    // Need to re-retrieve this, as a different show passphrase radio
    // button is shown for custom passphrase users.
    encryptWithPassphrase = encryptionElement.shadowRoot!.querySelector(
        'cr-radio-button[name="encrypt-with-passphrase"]')!;

    // Assert that the radio boxes are disabled after encryption enabled.
    assertTrue(encryptionRadioGroup.disabled);
    assertEquals(-1, encryptWithGoogle.$.button.tabIndex);
    assertEquals(-1, encryptWithPassphrase.$.button.tabIndex);
  });

  test('RadioBoxesHiddenWhenPassphraseRequired', function() {
    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    prefs.passphraseRequired = true;
    webUIListenerCallback('sync-prefs-changed', prefs);

    flush();

    assertTrue(
        syncPage.shadowRoot!
            .querySelector<HTMLElement>('#encryptionDescription')!.hidden);
    assertEquals(
        encryptionElement.shadowRoot!
            .querySelector<HTMLElement>(
                '#encryptionRadioGroupContainer')!.style.display,
        'none');
  });

  test('EnterPassphraseLabelWhenNoPassphraseTime', () => {
    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    prefs.passphraseRequired = true;
    webUIListenerCallback('sync-prefs-changed', prefs);
    flush();
    const enterPassphraseLabel =
        syncPage.shadowRoot!.querySelector<HTMLElement>(
            '#enterPassphraseLabel')!;

    assertEquals(
        'Your data is encrypted with your sync passphrase. Enter it to start' +
            ' sync.',
        enterPassphraseLabel.innerText);
  });

  test('EnterPassphraseLabelWhenHasPassphraseTime', () => {
    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    prefs.passphraseRequired = true;
    prefs.explicitPassphraseTime = 'Jan 01, 1970';
    webUIListenerCallback('sync-prefs-changed', prefs);
    flush();
    const enterPassphraseLabel =
        syncPage.shadowRoot!.querySelector<HTMLElement>(
            '#enterPassphraseLabel')!;

    assertEquals(
        `Your data was encrypted with your sync passphrase on ${
            prefs.explicitPassphraseTime}. Enter it to start sync.`,
        enterPassphraseLabel.innerText);
  });

  test(
      'ExistingPassphraseSubmitButtonDisabledWhenExistingPassphraseEmpty',
      async () => {
        const prefs = getSyncAllPrefs();
        prefs.encryptAllData = true;
        prefs.passphraseRequired = true;
        webUIListenerCallback('sync-prefs-changed', prefs);
        flush();

        const existingPassphraseInput =
            syncPage.shadowRoot!.querySelector<CrInputElement>(
                '#existingPassphraseInput');
        assertTrue(!!existingPassphraseInput);
        const submitExistingPassphrase =
            syncPage.shadowRoot!.querySelector<CrButtonElement>(
                '#submitExistingPassphrase')!;

        existingPassphraseInput.value = '';
        await existingPassphraseInput.updateComplete;
        assertTrue(submitExistingPassphrase.disabled);

        existingPassphraseInput.value = 'foo';
        await existingPassphraseInput.updateComplete;
        assertFalse(submitExistingPassphrase.disabled);
      });

  test('EnterExistingWrongPassphrase', async function() {
    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    prefs.passphraseRequired = true;
    webUIListenerCallback('sync-prefs-changed', prefs);
    flush();

    const existingPassphraseInput =
        syncPage.shadowRoot!.querySelector<CrInputElement>(
            '#existingPassphraseInput');
    assertTrue(!!existingPassphraseInput);
    existingPassphraseInput.value = 'wrong';
    browserProxy.decryptionPassphraseSuccess = false;

    const submitExistingPassphrase =
        syncPage.shadowRoot!.querySelector<CrButtonElement>(
            '#submitExistingPassphrase');
    assertTrue(!!submitExistingPassphrase);
    await existingPassphraseInput.updateComplete;
    submitExistingPassphrase!.click();

    const passphrase = await browserProxy.whenCalled('setDecryptionPassphrase');

    assertEquals('wrong', passphrase);
    assertTrue(existingPassphraseInput!.invalid);
  });

  test('EnterExistingCorrectPassphrase', async function() {
    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    prefs.passphraseRequired = true;
    webUIListenerCallback('sync-prefs-changed', prefs);
    flush();

    const existingPassphraseInput =
        syncPage.shadowRoot!.querySelector<CrInputElement>(
            '#existingPassphraseInput');
    assertTrue(!!existingPassphraseInput);
    existingPassphraseInput!.value = 'right';
    browserProxy.decryptionPassphraseSuccess = true;

    const submitExistingPassphrase =
        syncPage.shadowRoot!.querySelector<CrButtonElement>(
            '#submitExistingPassphrase');
    assertTrue(!!submitExistingPassphrase);
    await existingPassphraseInput.updateComplete;
    submitExistingPassphrase!.click();

    const passphrase = await browserProxy.whenCalled('setDecryptionPassphrase');

    assertEquals('right', passphrase);

    // Fake backend response.
    const newPrefs = getSyncAllPrefs();
    newPrefs.encryptAllData = true;
    webUIListenerCallback('sync-prefs-changed', newPrefs);

    flush();
    await eventToPromise('selected-changed', encryptionRadioGroup);

    // Verify that the encryption radio boxes are shown but disabled.
    assertTrue(encryptionRadioGroup.disabled);
    assertEquals(-1, encryptWithGoogle.$.button.tabIndex);
    assertEquals(-1, encryptWithPassphrase.$.button.tabIndex);

    // Confirm that the page navigates away form the sync setup.
    await browserProxy.whenCalled('didNavigateAwayFromSyncPage');
    const router = Router.getInstance();
    assertEquals(router.getRoutes().PEOPLE, router.getCurrentRoute());
  });

  test('EnterExistingPassphraseDoesNotExistIfSignedOut', async function() {
    syncPage.syncStatus = {
      signedInState: SignedInState.SIGNED_IN,
      disabled: false,
      hasError: true,
      statusAction: StatusAction.ENTER_PASSPHRASE,
    };

    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    prefs.passphraseRequired = true;
    webUIListenerCallback('sync-prefs-changed', prefs);
    flush();

    assertFalse(!!syncPage.shadowRoot!.querySelector<CrInputElement>(
        '#existingPassphraseInput'));
  });

  test('SyncAdvancedRow', function() {
    flush();

    const syncAdvancedRow =
        syncPage.shadowRoot!.querySelector<HTMLElement>('#sync-advanced-row')!;
    assertFalse(syncAdvancedRow.hidden);

    syncAdvancedRow.click();
    flush();

    assertEquals(
        routes.SYNC_ADVANCED.path, Router.getInstance().getCurrentRoute().path);
  });

  // This test checks whether the passphrase encryption options are
  // disabled. This is important for supervised accounts. Because sync
  // is required for supervision, passphrases should remain disabled.
  test('DisablingSyncPassphrase', async () => {
    // We initialize a new SyncPrefs object for each case, because
    // otherwise the webUIListener doesn't update.

    // 1) Normal user (full data encryption allowed)
    // EXPECTED: encryptionOptions enabled
    const prefs1 = getSyncAllPrefs();
    prefs1.customPassphraseAllowed = true;
    webUIListenerCallback('sync-prefs-changed', prefs1);
    syncPage.syncStatus = {
      supervisedUser: false,
      statusAction: StatusAction.NO_ACTION,
    };
    await microtasksFinished();

    assertFalse(encryptionRadioGroup.disabled);
    assertEquals(encryptWithGoogle.getAttribute('aria-disabled'), 'false');
    assertEquals(encryptWithPassphrase.getAttribute('aria-disabled'), 'false');

    // 2) Normal user (full data encryption not allowed)
    // customPassphraseAllowed is usually false only for supervised
    // users, but it's better to be check this case.
    // EXPECTED: encryptionOptions disabled
    const prefs2 = getSyncAllPrefs();
    prefs2.customPassphraseAllowed = false;
    webUIListenerCallback('sync-prefs-changed', prefs2);
    syncPage.syncStatus = {
      supervisedUser: false,
      statusAction: StatusAction.NO_ACTION,
    };
    await microtasksFinished();
    assertTrue(encryptionRadioGroup.disabled);
    assertEquals(encryptWithGoogle.getAttribute('aria-disabled'), 'true');
    assertEquals(encryptWithPassphrase.getAttribute('aria-disabled'), 'true');

    // 3) Supervised user (full data encryption not allowed)
    // EXPECTED: encryptionOptions disabled
    const prefs3 = getSyncAllPrefs();
    prefs3.customPassphraseAllowed = false;
    webUIListenerCallback('sync-prefs-changed', prefs3);
    syncPage.syncStatus = {
      supervisedUser: true,
      statusAction: StatusAction.NO_ACTION,
    };
    await microtasksFinished();
    assertTrue(encryptionRadioGroup.disabled);
    assertEquals(encryptWithGoogle.getAttribute('aria-disabled'), 'true');
    assertEquals(encryptWithPassphrase.getAttribute('aria-disabled'), 'true');

    // 4) Supervised user (full data encryption allowed)
    // This never happens in practice, but just to be safe.
    // EXPECTED: encryptionOptions disabled
    const prefs4 = getSyncAllPrefs();
    prefs4.customPassphraseAllowed = true;
    webUIListenerCallback('sync-prefs-changed', prefs4);
    syncPage.syncStatus = {
      supervisedUser: true,
      statusAction: StatusAction.NO_ACTION,
    };
    await microtasksFinished();
    assertTrue(encryptionRadioGroup.disabled);
    assertEquals(encryptWithGoogle.getAttribute('aria-disabled'), 'true');
    assertEquals(encryptWithPassphrase.getAttribute('aria-disabled'), 'true');
  });

  // The sync dashboard is not accessible by supervised
  // users, so it should remain hidden.
  test('SyncDashboardHiddenFromSupervisedUsers', function() {
    const dashboardLink =
        syncPage.shadowRoot!.querySelector<HTMLElement>('#syncDashboardLink')!;

    const prefs = getSyncAllPrefs();
    webUIListenerCallback('sync-prefs-changed', prefs);

    // Normal user
    syncPage.syncStatus = {
      supervisedUser: false,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertFalse(dashboardLink.hidden);

    // Supervised user
    syncPage.syncStatus = {
      supervisedUser: true,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertTrue(dashboardLink.hidden);
  });

  // ######################################
  // TESTS THAT ARE SKIPPED ON CHROMEOS ASH
  // ######################################


  // <if expr="not chromeos_ash">
  test('SyncSetupCancel', async function() {
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      firstSetupInProgress: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    simulateStoredAccounts([{email: '[email protected]'}]);

    const cancelButton =
        syncPage.shadowRoot!.querySelector('settings-sync-account-control')!
            .shadowRoot!.querySelector<HTMLElement>(
                '#setup-buttons cr-button:not(.action-button)');

    assertTrue(!!cancelButton);

    // Clicking the setup cancel button aborts sync.
    cancelButton!.click();
    const abort = await browserProxy.whenCalled('didNavigateAwayFromSyncPage');
    assertTrue(abort);
  });

  test('SyncSetupConfirm', async function() {
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      firstSetupInProgress: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    simulateStoredAccounts([{email: '[email protected]'}]);

    const confirmButton =
        syncPage.shadowRoot!.querySelector('settings-sync-account-control')!
            .shadowRoot!.querySelector<HTMLElement>(
                '#setup-buttons .action-button');

    assertTrue(!!confirmButton);
    confirmButton!.click();

    const abort = await browserProxy.whenCalled('didNavigateAwayFromSyncPage');
    assertFalse(abort);
  });

  test('SyncSetupLeavePage', async function() {
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      firstSetupInProgress: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();

    // Navigating away while setup is in progress opens the 'Cancel sync?'
    // dialog.
    const router = Router.getInstance();
    router.navigateTo(routes.BASIC);
    await eventToPromise('cr-dialog-open', syncPage);
    assertEquals(router.getRoutes().SYNC, router.getCurrentRoute());
    assertTrue(syncPage.shadowRoot!
                   .querySelector<CrDialogElement>('#setupCancelDialog')!.open);

    // Clicking the cancel button on the 'Cancel sync?' dialog closes
    // the dialog and removes it from the DOM.
    syncPage.shadowRoot!.querySelector<CrDialogElement>('#setupCancelDialog')!
        .querySelector<HTMLElement>('.cancel-button')!.click();
    await eventToPromise(
        'close',
        syncPage.shadowRoot!.querySelector<CrDialogElement>(
            '#setupCancelDialog')!);
    flush();
    assertEquals(router.getRoutes().SYNC, router.getCurrentRoute());
    assertFalse(!!syncPage.shadowRoot!.querySelector<CrDialogElement>(
        '#setupCancelDialog'));

    // Navigating away while setup is in progress opens the
    // dialog again.
    router.navigateTo(routes.BASIC);
    await eventToPromise('cr-dialog-open', syncPage);
    assertTrue(syncPage.shadowRoot!
                   .querySelector<CrDialogElement>('#setupCancelDialog')!.open);

    // Clicking the confirm button on the dialog aborts sync.
    syncPage.shadowRoot!.querySelector<CrDialogElement>('#setupCancelDialog')!
        .querySelector<HTMLElement>('.action-button')!.click();
    const abort = await browserProxy.whenCalled('didNavigateAwayFromSyncPage');
    assertTrue(abort);
  });

  // Tests that entering existing passhrase doesn't abort the sync setup.
  // Regression test for https://crbug.com/1279483.
  test('SyncSetupEnterExistingCorrectPassphrase', async function() {
    // Simulate sync setup in progress.
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      firstSetupInProgress: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    simulateStoredAccounts([{email: '[email protected]'}]);

    // Simulate passphrase enabled.
    const prefs = getSyncAllPrefs();
    prefs.encryptAllData = true;
    prefs.passphraseRequired = true;
    webUIListenerCallback('sync-prefs-changed', prefs);
    flush();

    // Enter and submit an existing passphrase.
    const existingPassphraseInput =
        syncPage.shadowRoot!.querySelector<CrInputElement>(
            '#existingPassphraseInput');
    assertTrue(!!existingPassphraseInput);
    existingPassphraseInput.value = 'right';
    browserProxy.decryptionPassphraseSuccess = true;
    const submitExistingPassphrase =
        syncPage.shadowRoot!.querySelector<CrButtonElement>(
            '#submitExistingPassphrase');
    await existingPassphraseInput.updateComplete;
    submitExistingPassphrase!.click();

    await browserProxy.whenCalled('setDecryptionPassphrase');
    // The sync setup cancel dialog would appear on next render if the sync
    // setup was stopped.
    await waitBeforeNextRender(syncPage);

    // Entering passphrase should not display the cancel dialog and should not
    // abort the sync setup.
    const router = Router.getInstance();
    assertEquals(router.getRoutes().SYNC, router.getCurrentRoute());
    const setupCancelDialog =
        syncPage.shadowRoot!.querySelector<CrDialogElement>(
            '#setupCancelDialog');
    assertFalse(!!setupCancelDialog);
  });

  // Tests that creating a new passhrase doesn't abort the sync setup.
  // Regression test for https://crbug.com/1279483.
  test('SyncSetupCreatingValidPassphrase', async function() {
    // Simulate sync setup in progress.
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      firstSetupInProgress: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    simulateStoredAccounts([{email: '[email protected]'}]);

    // Create and submit a new passphrase.
    encryptWithPassphrase.click();
    await eventToPromise('selected-changed', encryptionRadioGroup);
    const passphraseInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseInput')!;
    const passphraseConfirmationInput =
        encryptionElement.shadowRoot!.querySelector<CrInputElement>(
            '#passphraseConfirmationInput')!;
    passphraseInput.value = 'foo';
    passphraseConfirmationInput.value = 'foo';
    browserProxy.encryptionPassphraseSuccess = true;
    const saveNewPassphrase =
        encryptionElement.shadowRoot!.querySelector<CrButtonElement>(
            '#saveNewPassphrase');
    await Promise.all([
      passphraseInput.updateComplete,
      passphraseConfirmationInput.updateComplete,
    ]);
    saveNewPassphrase!.click();

    await browserProxy.whenCalled('setEncryptionPassphrase');
    // The sync setup cancel dialog would appear on next render if the sync
    // setup was stopped.
    await waitBeforeNextRender(syncPage);

    // Creating passphrase should not display the cancel dialog and should not
    // abort the sync setup.
    const router = Router.getInstance();
    assertEquals(router.getRoutes().SYNC, router.getCurrentRoute());
    const setupCancelDialog =
        syncPage.shadowRoot!.querySelector<CrDialogElement>(
            '#setupCancelDialog');
    assertFalse(!!setupCancelDialog);
  });

  test('SyncSetupSearchSettings', async function() {
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      firstSetupInProgress: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();

    // Searching settings while setup is in progress cancels sync.
    const router = Router.getInstance();
    router.navigateTo(
        router.getRoutes().BASIC, new URLSearchParams('search=foo'));

    const abort = await browserProxy.whenCalled('didNavigateAwayFromSyncPage');
    assertTrue(abort);
  });

  test('ShowAccountRow', function() {
    assertFalse(
        !!syncPage.shadowRoot!.querySelector('settings-sync-account-control'));
    syncPage.syncStatus = {
      syncSystemEnabled: false,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertFalse(
        !!syncPage.shadowRoot!.querySelector('settings-sync-account-control'));
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertTrue(
        !!syncPage.shadowRoot!.querySelector('settings-sync-account-control'));
  });

  test('ShowAccountRow_SigninAllowedFalse', function() {
    loadTimeData.overrideValues({signinAllowed: false});
    setupSyncPage();

    assertFalse(
        !!syncPage.shadowRoot!.querySelector('settings-sync-account-control'));
    syncPage.syncStatus = {
      syncSystemEnabled: false,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertFalse(
        !!syncPage.shadowRoot!.querySelector('settings-sync-account-control'));
    syncPage.syncStatus = {
      syncSystemEnabled: true,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();
    assertFalse(
        !!syncPage.shadowRoot!.querySelector('settings-sync-account-control'));
  });
  // </if>
});

suite('EEAChoiceCountry', function() {
  let syncPage: SettingsSyncPageElement;
  let openWindowProxy: TestOpenWindowProxy;
  let metricsBrowserProxy: TestMetricsBrowserProxy;

  suiteSetup(function() {
    loadTimeData.overrideValues({
      signinAllowed: true,
      isEeaChoiceCountry: true,
    });
  });

  setup(function() {
    metricsBrowserProxy = new TestMetricsBrowserProxy();
    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
    openWindowProxy = new TestOpenWindowProxy();
    OpenWindowProxyImpl.setInstance(openWindowProxy);

    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    syncPage = document.createElement('settings-sync-page');
    document.body.appendChild(syncPage);

    // Start with Sync All with no encryption selected. Also, ensure
    // that this is not a supervised user, so that Sync Passphrase is
    // enabled.
    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefs());
    syncPage.set('syncStatus', {
      signedInState: SignedInState.SYNCING,
      supervisedUser: false,
      statusAction: StatusAction.NO_ACTION,
    });
    flush();
  });

  teardown(function() {
    syncPage.remove();
  });

  test('personalizationControlsVisibility', function() {
    // TODO(crbug.com/324091979): Remove once crbug.com/324091979 launched.
    assertFalse(isChildVisible(syncPage, '#activityControlsLinkRowV1'));

    assertFalse(isChildVisible(syncPage, '#activityControlsLinkRowV2'));
    assertTrue(isChildVisible(syncPage, '#personalizationExpandButton'));
  });

  test('personalizationCollapse', async function() {
    // The collapse is collapsed by default.
    const personalizationCollapse =
        syncPage.shadowRoot!.querySelector<CrCollapseElement>(
            '#personalizationCollapse');
    assertTrue(!!personalizationCollapse);
    assertFalse(personalizationCollapse.opened);

    // Clicking the expand-button expands the collapse.
    const expandButton = syncPage.shadowRoot!.querySelector<HTMLElement>(
        '#personalizationExpandButton');
    assertTrue(!!expandButton);
    expandButton.click();
    await flushTasks();
    assertTrue(personalizationCollapse.opened);

    // Clicking the expand-button again collapses the collapse.
    expandButton.click();
    await flushTasks();
    assertFalse(personalizationCollapse.opened);
  });

  test('linkedServicesClick', async function() {
    // The linkedServices row is only visible when the collapse is expanded.
    const expandButton = syncPage.shadowRoot!.querySelector<HTMLElement>(
        '#personalizationExpandButton');
    assertTrue(!!expandButton);
    expandButton.click();
    await flushTasks();

    const linkedServicesLinkRow =
        syncPage.shadowRoot!.querySelector<HTMLElement>(
            '#linkedServicesLinkRow');
    assertTrue(!!linkedServicesLinkRow);
    linkedServicesLinkRow.click();
    assertEquals(
        'Sync_OpenLinkedServicesPage',
        await metricsBrowserProxy.whenCalled('recordAction'));
    const url = await openWindowProxy.whenCalled('openUrl');
    assertEquals(loadTimeData.getString('linkedServicesUrl'), url);
  });
});

// TODO(crbug.com/324091979): Remove once crbug.com/324091979 launched.
suite('LinkedServicesDisabled', function() {
  let syncPage: SettingsSyncPageElement;

  suiteSetup(function() {
    loadTimeData.overrideValues({
      signinAllowed: true,
      enableLinkedServicesSetting: false,
    });
  });

  setup(function() {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    syncPage = document.createElement('settings-sync-page');
    document.body.appendChild(syncPage);

    // Start with Sync All with no encryption selected. Also, ensure
    // that this is not a supervised user, so that Sync Passphrase is
    // enabled.
    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefs());
    syncPage.set('syncStatus', {
      signedInState: SignedInState.SYNCING,
      supervisedUser: false,
      statusAction: StatusAction.NO_ACTION,
    });
    flush();
  });

  teardown(function() {
    syncPage.remove();
  });

  test('personalizationControlsVisibility', function() {
    assertTrue(isChildVisible(syncPage, '#activityControlsLinkRowV1'));
    assertFalse(isChildVisible(syncPage, '#activityControlsLinkRowV2'));
    assertFalse(isChildVisible(syncPage, '#personalizationExpandButton'));
  });
});