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

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

import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import type {SettingsSyncControlsElement} from 'chrome://settings/lazy_load.js';
import type {CrLinkRowElement, CrRadioButtonElement, CrToggleElement, SyncPrefs} from 'chrome://settings/settings.js';
import {Router, SignedInState, StatusAction, SyncBrowserProxyImpl} from 'chrome://settings/settings.js';
import {assertEquals, assertDeepEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {waitBeforeNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';

import {getSyncAllPrefs, getSyncAllPrefsManaged} from './sync_test_util.js';
import {TestSyncBrowserProxy} from './test_sync_browser_proxy.js';

// <if expr="chromeos_lacros">
import {loadTimeData} from 'chrome://settings/settings.js';
// </if>

// clang-format on

suite('SyncControlsTest', async function() {
  let syncControls: SettingsSyncControlsElement;
  let browserProxy: TestSyncBrowserProxy;
  let syncEverything: CrRadioButtonElement;
  let customizeSync: CrRadioButtonElement;
  let radioGroup: HTMLElement;

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

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

    // Start with Sync All.
    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefs());
    flush();

    await waitBeforeNextRender(syncControls);
    syncEverything = syncControls.shadowRoot!.querySelector(
        'cr-radio-button[name="sync-everything"]')!;
    customizeSync = syncControls.shadowRoot!.querySelector(
        'cr-radio-button[name="customize-sync"]')!;
    const group = syncControls.shadowRoot!.querySelector('cr-radio-group');
    assertTrue(!!group);
    radioGroup = group;
    assertTrue(!!customizeSync);
    assertTrue(!!radioGroup);
  });

  function assertPrefs(
      prefs: SyncPrefs, datatypeControls: NodeListOf<CrToggleElement>) {
    const expected = getSyncAllPrefs();
    expected.syncAllDataTypes = false;
    assertDeepEquals(expected, prefs);

    webUIListenerCallback('sync-prefs-changed', expected);

    // Assert that all the individual datatype controls are checked and enabled.
    for (const control of datatypeControls) {
      // if lacros we check that Apps sync toggle is disabled when
      // kSyncChromeOSAppsToggleSharing feature is enabled.
      // <if expr="chromeos_lacros">
      if (control.id === 'appsSyncToggle') {
        const showSyncSettingsRevamp =
            loadTimeData.getBoolean('showSyncSettingsRevamp');
        assertEquals(control.disabled, showSyncSettingsRevamp);
        assertTrue(control.checked);
        continue;
      }
      // </if>
      assertFalse(control.disabled);
      assertTrue(control.checked);
    }

    // Assert that all policy indicators are hidden.
    const policyIndicators =
        syncControls.shadowRoot!.querySelectorAll('cr-policy-indicator');
    assertTrue(policyIndicators.length > 0);
    for (const indicator of policyIndicators) {
      assertFalse(isVisible(indicator));
    }

    browserProxy.resetResolver('setSyncDatatypes');
  }

  test('SettingIndividualDatatypes', async function() {
    assertTrue(syncEverything.checked);
    assertFalse(customizeSync.checked);
    assertEquals(
        syncControls.shadowRoot!.querySelector('#syncAllDataTypesControl'),
        null);

    // Assert that all the individual datatype controls are disabled.
    const datatypeControls =
        syncControls.shadowRoot!.querySelectorAll<CrToggleElement>(
            '.list-item:not([hidden]) > cr-toggle');

    assertTrue(datatypeControls.length > 0);
    for (const control of datatypeControls) {
      assertTrue(control.disabled);
      assertTrue(control.checked);
    }

    customizeSync.click();
    await eventToPromise('selected-changed', radioGroup);
    assertFalse(syncEverything.checked);
    assertTrue(customizeSync.checked);

    const prefs = await browserProxy.whenCalled('setSyncDatatypes');
    assertPrefs(prefs, datatypeControls);
  });

  test('OsSyncSettingsLink', function() {
    // Check that the os sync settings link
    // is available only in Lacros and kSyncChromeOSAppsToggleSharing
    // feature is enabled.
    const osSyncSettingsLink: CrLinkRowElement =
        syncControls.shadowRoot!.querySelector('#osSyncSettingsLink')!;

    // <if expr="chromeos_lacros">
    assertTrue(!!osSyncSettingsLink);
    const showSyncSettingsRevamp =
        loadTimeData.getBoolean('showSyncSettingsRevamp');
    assertEquals(osSyncSettingsLink.hidden, !showSyncSettingsRevamp);
    // </if>

    // <if expr="not chromeos_lacros">
    assertFalse(!!osSyncSettingsLink);
    // </if>
  });

  test('SignedIn', function() {
    // Controls are available by default.
    assertFalse(syncControls.hidden);

    syncControls.syncStatus = {
      disabled: false,
      hasError: false,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    // Controls are available when signed in and there is no error.
    assertFalse(syncControls.hidden);
  });

  test('SyncDisabled', function() {
    syncControls.syncStatus = {
      disabled: true,
      hasError: false,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    // Controls are hidden when sync is disabled.
    assertTrue(syncControls.hidden);
  });

  test('SyncError', function() {
    syncControls.syncStatus = {
      disabled: false,
      hasError: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    // Controls are hidden when there is an error but it's not a
    // passphrase error.
    assertTrue(syncControls.hidden);

    syncControls.syncStatus = {
      disabled: false,
      hasError: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.ENTER_PASSPHRASE,
    };
    // Controls are available when there is a passphrase error.
    assertFalse(syncControls.hidden);
  });

  // <if expr="chromeos_ash">
  test('SyncCookiesSupported', async function() {
    // Sync everything enabled.
    assertTrue(syncEverything.checked);
    assertFalse(customizeSync.checked);

    // The cookies element is not visible when syncCookiesSupported is disabled
    // (default).
    let cookieListItem = syncControls.shadowRoot!.querySelector(
        '#cookiesSyncItem:not([hidden])');
    assertFalse(!!cookieListItem);

    // Enable syncCookiesSupported.
    syncControls.syncStatus = {
      disabled: false,
      hasError: false,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
      syncCookiesSupported: true,
    };
    // The cookies element is now visible.
    cookieListItem = syncControls.shadowRoot!.querySelector(
        '#cookiesSyncItem:not([hidden])');
    assertTrue(!!cookieListItem);
    // Cookies checkbox is disabled.
    let cookiesCheckbox: CrToggleElement =
        syncControls.shadowRoot!.querySelector('#cookiesCheckbox')!;
    assertTrue(!!cookiesCheckbox);
    assertTrue(cookiesCheckbox.disabled);
    assertTrue(cookiesCheckbox.checked);

    // Customize sync enabled.
    customizeSync.click();
    await eventToPromise('selected-changed', radioGroup);
    assertFalse(syncEverything.checked);
    assertTrue(customizeSync.checked);

    // Cookies checkbox is enabled.
    cookiesCheckbox =
        syncControls.shadowRoot!.querySelector('#cookiesCheckbox')!;
    assertTrue(!!cookiesCheckbox);
    assertFalse(cookiesCheckbox.disabled);
    assertTrue(cookiesCheckbox.checked);
  });
  // </if>
});

suite('SyncControlsSubpageTest', function() {
  let syncControls: SettingsSyncControlsElement;
  let browserProxy: TestSyncBrowserProxy;

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

    document.body.innerHTML = window.trustedTypes!.emptyHTML;

    syncControls = document.createElement('settings-sync-controls');
    const router = Router.getInstance();
    router.navigateTo(router.getRoutes().SYNC_ADVANCED);
    document.body.appendChild(syncControls);

    syncControls.syncStatus = {
      disabled: false,
      hasError: false,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
    };
    flush();

    assertEquals(router.getRoutes().SYNC_ADVANCED, router.getCurrentRoute());
  });

  test('SignedOut', function() {
    syncControls.syncStatus = {
      disabled: false,
      hasError: false,
      signedInState: SignedInState.SIGNED_OUT,
      statusAction: StatusAction.NO_ACTION,
    };
    const router = Router.getInstance();
    assertEquals(router.getRoutes().SYNC.path, router.getCurrentRoute().path);
  });

  test('PassphraseError', function() {
    syncControls.syncStatus = {
      disabled: false,
      hasError: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.ENTER_PASSPHRASE,
    };
    const router = Router.getInstance();
    assertEquals(
        router.getRoutes().SYNC_ADVANCED.path, router.getCurrentRoute().path);
  });

  test('SyncPaused', function() {
    syncControls.syncStatus = {
      disabled: false,
      hasError: true,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.REAUTHENTICATE,
    };
    const router = Router.getInstance();
    assertEquals(router.getRoutes().SYNC.path, router.getCurrentRoute().path);
  });
});

// Test to check that toggles are disabled when sync types are managed by
// policy.
suite('SyncControlsManagedTest', async function() {
  let syncControls: SettingsSyncControlsElement;
  let browserProxy: TestSyncBrowserProxy;
  let syncEverything: CrRadioButtonElement;
  let customizeSync: CrRadioButtonElement;
  let radioGroup: HTMLElement;

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

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

    // Start with all prefs managed.
    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefsManaged());
    // Enable Cookie sync.
    syncControls.syncStatus = {
      disabled: false,
      hasError: false,
      signedInState: SignedInState.SYNCING,
      statusAction: StatusAction.NO_ACTION,
      syncCookiesSupported: true,
    };
    flush();

    await waitBeforeNextRender(syncControls);
    syncEverything = syncControls.shadowRoot!.querySelector(
        'cr-radio-button[name="sync-everything"]')!;
    customizeSync = syncControls.shadowRoot!.querySelector(
        'cr-radio-button[name="customize-sync"]')!;
    const group = syncControls.shadowRoot!.querySelector('cr-radio-group');
    assertTrue(!!group);
    radioGroup = group;
    assertTrue(!!syncEverything);
    assertTrue(!!customizeSync);
  });

  test('SettingIndividualDatatypesManaged', async function() {
    // The syncEverything and customizeSync buttons should not be affected by
    // the managed state.
    assertTrue(syncEverything.checked);
    assertFalse(customizeSync.checked);

    const datatypeControls =
        syncControls.shadowRoot!.querySelectorAll<CrToggleElement>(
            '.list-item:not([hidden]) > cr-toggle');
    assertTrue(datatypeControls.length > 0);

    // Assert that all toggles have the policy indicator icon visible when they
    // are all managed.
    const policyIndicators =
        syncControls.shadowRoot!.querySelectorAll('cr-policy-indicator');
    assertTrue(policyIndicators.length > 0);
    for (const indicator of policyIndicators) {
      // On Lacros, if kSyncChromeOSAppsToggleSharing is enabled, syncing of Apps
      // is controlled via the OS, not the browser. So in that case, the policy
      // indicator should not be visible here.
      // <if expr="chromeos_lacros">
      if (indicator.id === 'appsSyncPolicyIndicator') {
        const showSyncSettingsRevamp =
            loadTimeData.getBoolean('showSyncSettingsRevamp');
        assertEquals(isVisible(indicator), !showSyncSettingsRevamp);
        continue;
      }
      // </if>
      assertTrue(isVisible(indicator));
    }

    // Assert that all the individual datatype controls are disabled and
    // unchecked.
    for (const control of datatypeControls) {
      assertTrue(control.disabled);
      assertFalse(control.checked);
    }

    customizeSync.click();
    await eventToPromise('selected-changed', radioGroup);
    assertFalse(syncEverything.checked);
    assertTrue(customizeSync.checked);

    const prefs = await browserProxy.whenCalled('setSyncDatatypes');

    const expected = getSyncAllPrefsManaged();
    expected.syncAllDataTypes = false;
    assertDeepEquals(expected, prefs);

    webUIListenerCallback('sync-prefs-changed', expected);

    // Assert that all the individual datatype controls are still unchecked and
    // disabled.
    for (const control of datatypeControls) {
      assertTrue(control.disabled);
      assertFalse(control.checked);
    }
    browserProxy.resetResolver('setSyncDatatypes');
  });
});

suite('AutofillAndPaymentsToggles', async function() {
  let autofillCheckbox: CrToggleElement;
  let paymentsCheckbox: CrToggleElement;

  function updateComplete(): Promise<void> {
    return Promise
        .all([
          autofillCheckbox.updateComplete,
          paymentsCheckbox.updateComplete,
        ])
        .then(() => {});
  }

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

    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    const syncControls = document.createElement('settings-sync-controls');
    document.body.appendChild(syncControls);

    webUIListenerCallback('sync-prefs-changed', getSyncAllPrefs());
    flush();

    await waitBeforeNextRender(syncControls);
    const customizeSync: CrRadioButtonElement =
        syncControls.shadowRoot!.querySelector(
            'cr-radio-button[name="customize-sync"]')!;
    const radioGroup = syncControls.shadowRoot!.querySelector('cr-radio-group');
    autofillCheckbox =
        syncControls.shadowRoot!.querySelector('#autofillCheckbox')!;
    paymentsCheckbox =
        syncControls.shadowRoot!.querySelector('#paymentsCheckbox')!;
    assertTrue(!!customizeSync);
    assertTrue(!!radioGroup);
    assertTrue(!!autofillCheckbox);
    assertTrue(!!paymentsCheckbox);

    customizeSync.click();
    await eventToPromise('selected-changed', radioGroup);
    assertTrue(customizeSync.checked);
    assertTrue(autofillCheckbox.checked);
    assertTrue(paymentsCheckbox.checked);
  });

  // Before crbug.com/40265120, the autofill and payments toggles used to be
  // coupled. This test verifies they no longer are.
  test('DecoupledAutofillPaymentsToggles', async function() {
    // Disable Autofill sync.
    autofillCheckbox.click();
    await updateComplete();
    assertFalse(autofillCheckbox.checked);
    assertTrue(paymentsCheckbox.checked);
    assertFalse(paymentsCheckbox.disabled);

    // Disable Payment methods sync.
    paymentsCheckbox.click();
    await updateComplete();
    assertFalse(autofillCheckbox.checked);
    assertFalse(paymentsCheckbox.checked);
    assertFalse(paymentsCheckbox.disabled);

    // Enable Autofill sync.
    autofillCheckbox.click();
    await updateComplete();
    assertTrue(autofillCheckbox.checked);
    assertFalse(paymentsCheckbox.checked);
    assertFalse(paymentsCheckbox.disabled);
  });
});