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

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

import {DateTimeSettingsCardElement, SettingsDateTimePageElement, TimeZoneAutoDetectMethod, TimezoneSubpageElement} from 'chrome://os-settings/lazy_load.js';
import {ControlledRadioButtonElement, CrSettingsPrefs, DateTimeBrowserProxy, GeolocationAccessLevel, PrefsState, Router, routes, SettingsDropdownMenuElement, SettingsToggleButtonElement} from 'chrome://os-settings/os_settings.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 {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';

import {TestDateTimeBrowserProxy} from './test_date_time_browser_proxy.js';

function getFakePrefs() {
  return {
    cros: {
      system: {
        timezone: {
          key: 'cros.system.timezone',
          type: chrome.settingsPrivate.PrefType.STRING,
          value: 'Westeros/Kings_Landing',
        },
      },
      flags: {
        // TODO(alemate): This test should be run for all possible
        // combinations of values of these options.
        per_user_timezone_enabled: {
          key: 'cros.flags.per_user_timezone_enabled',
          type: chrome.settingsPrivate.PrefType.BOOLEAN,
          value: true,
        },
        fine_grained_time_zone_detection_enabled: {
          key: 'cros.flags.fine_grained_time_zone_detection_enabled',
          type: chrome.settingsPrivate.PrefType.BOOLEAN,
          value: true,
        },
      },
    },
    settings: {
      clock: {
        use_24hour_clock: {
          key: 'settings.clock.use_24hour_clock',
          type: chrome.settingsPrivate.PrefType.BOOLEAN,
          value: false,
        },
      },
      timezone: {
        key: 'settings.timezone',
        type: chrome.settingsPrivate.PrefType.STRING,
        value: 'Westeros/Kings_Landing',
      },
    },
    generated: {
      resolve_timezone_by_geolocation_method_short: {
        key: 'generated.resolve_timezone_by_geolocation_method_short',
        type: chrome.settingsPrivate.PrefType.NUMBER,
        value: TimeZoneAutoDetectMethod.IP_ONLY,
      },
      resolve_timezone_by_geolocation_on_off: {
        key: 'generated.resolve_timezone_by_geolocation_on_off',
        type: chrome.settingsPrivate.PrefType.BOOLEAN,
        value: true,
      },
    },
    ash: {
      user: {
        geolocation_access_level: {
          key: 'ash.user.geolocation_access_level',
          type: GeolocationAccessLevel,
          value: GeolocationAccessLevel.ALLOWED,
        },
      },
    },
  };
}

function updatePrefsWithPolicy(
    prefs: PrefsState, managed: boolean, valueFromPolicy: boolean): Object {
  const prefsCopy = structuredClone(prefs) as any;
  if (managed) {
    prefsCopy.generated.resolve_timezone_by_geolocation_method_short
        .controlledBy = chrome.settingsPrivate.ControlledBy.USER_POLICY;
    prefsCopy.generated.resolve_timezone_by_geolocation_method_short
        .enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
    prefsCopy.generated.resolve_timezone_by_geolocation_method_short.value =
        valueFromPolicy ? TimeZoneAutoDetectMethod.IP_ONLY :
                          TimeZoneAutoDetectMethod.DISABLED;

    prefsCopy.generated.resolve_timezone_by_geolocation_on_off.controlledBy =
        chrome.settingsPrivate.ControlledBy.USER_POLICY;
    prefsCopy.generated.resolve_timezone_by_geolocation_on_off.enforcement =
        chrome.settingsPrivate.Enforcement.ENFORCED;
    prefsCopy.generated.resolve_timezone_by_geolocation_on_off.value =
        valueFromPolicy;

    prefsCopy.settings.timezone.controlledBy =
        chrome.settingsPrivate.ControlledBy.USER_POLICY;
    prefsCopy.settings.timezone.enforcement =
        chrome.settingsPrivate.Enforcement.ENFORCED;
  } else {
    prefsCopy.generated.resolve_timezone_by_geolocation_on_off.controlledBy =
        undefined;
    prefsCopy.generated.resolve_timezone_by_geolocation_on_off.enforcement =
        undefined;
    // Auto-resolve defaults to true.
    prefsCopy.generated.resolve_timezone_by_geolocation_on_off.value = true;

    prefsCopy.generated.resolve_timezone_by_geolocation_method_short
        .controlledBy = undefined;
    prefsCopy.generated.resolve_timezone_by_geolocation_method_short
        .enforcement = undefined;
    // Auto-resolve defaults to true.
    prefsCopy.generated.resolve_timezone_by_geolocation_method_short.value =
        TimeZoneAutoDetectMethod.IP_ONLY;

    prefsCopy.settings.timezone.controlledBy = undefined;
    prefsCopy.settings.timezone.enforcement = undefined;
  }
  return prefsCopy;
}

// CrOS sends time zones as [id, friendly name] pairs.
const fakeTimezones = [
  ['Westeros/Highgarden', '(KNG-2:00) The Reach Time (Highgarden)'],
  ['Westeros/Winterfell', '(KNG-1:00) The North Time (Winterfell)'],
  [
    'Westeros/Kings_Landing',
    '(KNG+0:00) Westeros Standard Time (King\'s Landing)',
  ],
  ['Westeros/TheEyrie', '(KNG+1:00) The Vale Time (The Eyrie)'],
  ['Westeros/Sunspear', '(KNG+2:00) Dorne Time (Sunspear)'],
  ['FreeCities/Pentos', '(KNG+6:00) Pentos Time (Pentos)'],
  ['FreeCities/Volantis', '(KNG+9:00) Volantis Time (Volantis)'],
  ['BayOfDragons/Daenerys', '(KNG+14:00) Daenerys Free Time (Meereen)'],
];

/**
 * Sets up fakes and creates the date time element.
 */
function initializeDateTime(
    prefs: {[key: string]: any}, hasPolicy: boolean,
    autoDetectPolicyValue?: boolean): SettingsDateTimePageElement|
    TimezoneSubpageElement {
  // Find the desired initial time zone by ID.
  const timeZone = fakeTimezones.find(
      (timeZonePair) =>
          timeZonePair[0] === prefs['cros'].system.timezone.value);

  assertTrue(!!timeZone);
  const data = {
    timeZoneID: timeZone[0],
    timeZoneName: timeZone[1],
    controlledSettingPolicy: 'This setting is managed by your administrator',
    setTimeZoneAutomaticallyDisabled: 'Automatic time zone detection disabled.',
    setTimeZoneAutomaticallyIpOnlyDefault:
        'Automatic time zone detection IP-only.',
    setTimeZoneAutomaticallyWithWiFiAccessPointsData:
        'Automatic time zone detection with WiFi AP',
    setTimeZoneAutomaticallyWithAllLocationInfo:
        'Automatic time zone detection with all location info',
    isChild: false,
  };

  loadTimeData.overrideValues(data);

  const dateTime: SettingsDateTimePageElement|TimezoneSubpageElement =
      prefs['cros'].flags.fine_grained_time_zone_detection_enabled.value ?
      document.createElement('timezone-subpage') :
      document.createElement('settings-date-time-page');
  dateTime.prefs =
      updatePrefsWithPolicy(prefs, hasPolicy, autoDetectPolicyValue || false);
  dateTime.activeTimeZoneDisplayName =
      dateTime.prefs.cros.system.timezone.value;

  CrSettingsPrefs.setInitialized();

  document.body.appendChild(dateTime);
  return dateTime;
}

suite('<settings-date-time-page>', () => {
  let dateTime: SettingsDateTimePageElement|TimezoneSubpageElement;
  let testBrowserProxy: TestDateTimeBrowserProxy;

  setup(() => {
    testBrowserProxy = new TestDateTimeBrowserProxy({fakeTimezones});
    DateTimeBrowserProxy.setInstanceForTesting(testBrowserProxy);
    CrSettingsPrefs.resetForTesting();
  });

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

  function getDateTimeSettingsCard(): DateTimeSettingsCardElement {
    const dateTimeSettingsCard =
        dateTime.shadowRoot!.querySelector('date-time-settings-card');
    assertTrue(!!dateTimeSettingsCard);
    return dateTimeSettingsCard;
  }

  function getTimeZoneAutoDetectToggle(): SettingsToggleButtonElement {
    const dateTimeSettingsCard = getDateTimeSettingsCard();
    const timeZoneAutoDetectToggle =
        dateTimeSettingsCard.shadowRoot!
            .querySelector<SettingsToggleButtonElement>(
                '#timeZoneAutoDetectToggle');
    assertTrue(!!timeZoneAutoDetectToggle);
    return timeZoneAutoDetectToggle;
  }

  function getTimeZoneAutoDetectOn(): ControlledRadioButtonElement {
    const timeZoneAutoDetectOn =
        dateTime.shadowRoot!.querySelector<ControlledRadioButtonElement>(
            '#timeZoneAutoDetectOn');
    assertTrue(!!timeZoneAutoDetectOn);
    return timeZoneAutoDetectOn;
  }

  function getTimeZoneAutoDetectOff(): ControlledRadioButtonElement {
    const timeZoneAutoDetectOff =
        dateTime.shadowRoot!.querySelector<ControlledRadioButtonElement>(
            '#timeZoneAutoDetectOff');
    assertTrue(!!timeZoneAutoDetectOff);
    return timeZoneAutoDetectOff;
  }

  function clickDisableAutoDetect(): void {
    if (dateTime.prefs.cros.flags.fine_grained_time_zone_detection_enabled
            .value) {
      getTimeZoneAutoDetectOff().click();
    } else {
      getTimeZoneAutoDetectToggle().click();
    }
  }

  function clickEnableAutoDetect(): void {
    if (dateTime.prefs.cros.flags.fine_grained_time_zone_detection_enabled
            .value) {
      getTimeZoneAutoDetectOn().click();
    } else {
      getTimeZoneAutoDetectToggle().click();
    }
  }

  function getAutodetectOnButton(): SettingsToggleButtonElement|
      ControlledRadioButtonElement {
    if (dateTime.prefs.cros.flags.fine_grained_time_zone_detection_enabled
            .value) {
      return getTimeZoneAutoDetectOn();
    }
    return getTimeZoneAutoDetectToggle();
  }

  function getTimeZoneSelector(id: string): SettingsDropdownMenuElement {
    const timezoneSelector =
        dateTime.shadowRoot!.querySelector('timezone-selector');
    assertTrue(!!timezoneSelector);
    const selectorId =
        timezoneSelector.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            id);
    assertTrue(!!selectorId);
    return selectorId;
  }

  function verifyAutoDetectSetting(
      autoDetect: boolean, managed: boolean): void {
    const selector = getTimeZoneSelector('#userTimeZoneSelector');
    assertEquals(autoDetect, selector.disabled);

    const checkButton = getAutodetectOnButton();
    const checkButtonChecked = checkButton ? checkButton.checked : false;
    if (!managed) {
      assertEquals(autoDetect, checkButtonChecked);
    }
  }

  function verifyTimeZonesPopulated(populated: boolean): void {
    const userTimezoneDropdown = getTimeZoneSelector('#userTimeZoneSelector');
    assertTrue(!!userTimezoneDropdown);
    if (populated) {
      assertEquals(
          fakeTimezones.length, userTimezoneDropdown.menuOptions.length);
    } else {
      assertEquals(1, userTimezoneDropdown.menuOptions.length);
    }
  }

  function updatePolicy(
      dateTime: SettingsDateTimePageElement|TimezoneSubpageElement,
      managed: boolean, valueFromPolicy: boolean): void {
    dateTime.prefs =
        updatePrefsWithPolicy(dateTime.prefs, managed, valueFromPolicy);
  }

  test('auto-detect on', async () => {
    const prefs = getFakePrefs();
    dateTime = initializeDateTime(prefs, false);
    flush();
    const resolveMethodDropdown =
        dateTime.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            '#timeZoneResolveMethodDropdown');

    assertTrue(!!resolveMethodDropdown);
    assertEquals(0, testBrowserProxy.handler.getCallCount('getTimezones'));

    verifyAutoDetectSetting(true, false);
    assertFalse(resolveMethodDropdown.disabled);
    verifyTimeZonesPopulated(false);

    clickDisableAutoDetect();
    assertEquals(1, testBrowserProxy.handler.getCallCount('getTimezones'));
    await flushTasks();

    verifyAutoDetectSetting(false, false);
    assertTrue(resolveMethodDropdown.disabled);

    verifyTimeZonesPopulated(true);
  });

  test('auto-detect off', async () => {
    dateTime = initializeDateTime(getFakePrefs(), false);
    const resolveMethodDropdown =
        dateTime.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            '#timeZoneResolveMethodDropdown');

    assertTrue(!!resolveMethodDropdown);
    dateTime.set(
        'prefs.generated.resolve_timezone_by_geolocation_on_off.value', false);
    dateTime.set(
        'prefs.generated.resolve_timezone_by_geolocation_method_short.value',
        TimeZoneAutoDetectMethod.DISABLED);

    assertEquals(1, testBrowserProxy.handler.getCallCount('getTimezones'));
    await flushTasks();

    verifyAutoDetectSetting(false, false);
    assertTrue(resolveMethodDropdown.disabled);
    verifyTimeZonesPopulated(true);

    clickEnableAutoDetect();

    verifyAutoDetectSetting(true, false);
    assertFalse(resolveMethodDropdown.disabled);
  });

  test('auto-detect forced on', async () => {
    const prefs = getFakePrefs();
    dateTime = initializeDateTime(prefs, true, true);
    flush();
    const resolveMethodDropdown =
        dateTime.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            '#timeZoneResolveMethodDropdown');

    assertTrue(!!resolveMethodDropdown);
    assertEquals(0, testBrowserProxy.handler.getCallCount('getTimezones'));

    verifyAutoDetectSetting(true, true);
    assertFalse(resolveMethodDropdown.disabled);
    verifyTimeZonesPopulated(false);

    // Cannot disable auto-detect.
    clickDisableAutoDetect();

    verifyAutoDetectSetting(true, true);
    assertFalse(resolveMethodDropdown.disabled);
    assertEquals(0, testBrowserProxy.handler.getCallCount('getTimezones'));

    // Update the policy: force auto-detect off.
    updatePolicy(dateTime, true, false);
    assertEquals(1, testBrowserProxy.handler.getCallCount('getTimezones'));
    await flushTasks();

    verifyAutoDetectSetting(false, true);
    assertTrue(resolveMethodDropdown.disabled);

    verifyTimeZonesPopulated(true);
  });

  test('auto-detect forced off', async () => {
    const prefs = getFakePrefs();
    dateTime = initializeDateTime(prefs, true, false);
    assertEquals(1, testBrowserProxy.handler.getCallCount('getTimezones'));
    await flushTasks();

    const resolveMethodDropdown =
        dateTime.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            '#timeZoneResolveMethodDropdown');

    assertTrue(!!resolveMethodDropdown);
    assertEquals(1, testBrowserProxy.handler.getCallCount('getTimezones'));

    verifyAutoDetectSetting(false, true);
    assertTrue(resolveMethodDropdown.disabled);
    verifyTimeZonesPopulated(true);

    // Remove the policy so user's preference takes effect.
    updatePolicy(dateTime, false, false);

    verifyAutoDetectSetting(true, false);
    assertFalse(resolveMethodDropdown.disabled);

    // User can disable auto-detect.
    clickDisableAutoDetect();

    verifyAutoDetectSetting(false, false);
  });

  test('auto-detect on supervised account', async () => {
    const prefs = getFakePrefs();
    dateTime = initializeDateTime(prefs, false);
    // Set auto detect on.
    dateTime.set(
        'prefs.generated.resolve_timezone_by_geolocation_on_off.value', true);
    dateTime.set(
        'prefs.generated.resolve_timezone_by_geolocation_method_short.value',
        TimeZoneAutoDetectMethod.IP_ONLY);

    // Set fake child account.
    loadTimeData.overrideValues({
      isChild: true,
    });

    await Router.getInstance().navigateTo(routes.DATETIME_TIMEZONE_SUBPAGE);

    const resolveMethodDropdown =
        dateTime.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            '#timeZoneResolveMethodDropdown');
    const timezoneSelector = getTimeZoneSelector('#userTimeZoneSelector');
    const timeZoneAutoDetectOn =
        dateTime.shadowRoot!.querySelector<ControlledRadioButtonElement>(
            '#timeZoneAutoDetectOn');
    const timeZoneAutoDetectOff =
        dateTime.shadowRoot!.querySelector<ControlledRadioButtonElement>(
            '#timeZoneAutoDetectOff');

    assertTrue(!!resolveMethodDropdown);
    assertTrue(!!timeZoneAutoDetectOn);
    assertTrue(!!timeZoneAutoDetectOff);

    // Verify elements are disabled for child account.
    assertTrue(resolveMethodDropdown.disabled);
    assertTrue(timezoneSelector.disabled);
    assertTrue(timeZoneAutoDetectOn.disabled);
    assertTrue(timeZoneAutoDetectOff.disabled);

    assertEquals(
        1,
        testBrowserProxy.handler.getCallCount('showParentAccessForTimezone'));
    testBrowserProxy.observerRemote.onParentAccessValidationComplete(
        /*success=*/ true);
    await flushTasks();

    // Verify elements are enabled.
    assertFalse(resolveMethodDropdown.disabled);
    assertFalse(timeZoneAutoDetectOn.disabled);
    assertFalse(timeZoneAutoDetectOff.disabled);

    // |timezoneSelector| is disabled when auto detect on.
    assertTrue(timezoneSelector.disabled);
  });

  test('auto-detect off supervised account', async () => {
    const prefs = getFakePrefs();
    dateTime = initializeDateTime(prefs, false);
    // Set auto detect off.
    dateTime.set(
        'prefs.generated.resolve_timezone_by_geolocation_on_off.value', false);
    dateTime.set(
        'prefs.generated.resolve_timezone_by_geolocation_method_short.value',
        TimeZoneAutoDetectMethod.DISABLED);
    // Set fake child account.
    loadTimeData.overrideValues({
      isChild: true,
    });

    await Router.getInstance().navigateTo(routes.DATETIME_TIMEZONE_SUBPAGE);

    const resolveMethodDropdown =
        dateTime.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            '#timeZoneResolveMethodDropdown');
    const timezoneSelector = getTimeZoneSelector('#userTimeZoneSelector');
    const timeZoneAutoDetectOn =
        dateTime.shadowRoot!.querySelector<ControlledRadioButtonElement>(
            '#timeZoneAutoDetectOn');
    const timeZoneAutoDetectOff =
        dateTime.shadowRoot!.querySelector<ControlledRadioButtonElement>(
            '#timeZoneAutoDetectOff');

    assertTrue(!!resolveMethodDropdown);
    assertTrue(!!timeZoneAutoDetectOn);
    assertTrue(!!timeZoneAutoDetectOff);

    // Verify elements are disabled for child account.
    assertTrue(resolveMethodDropdown.disabled);
    assertTrue(timezoneSelector.disabled);
    assertTrue(timeZoneAutoDetectOn.disabled);
    assertTrue(timeZoneAutoDetectOff.disabled);

    assertEquals(
        1,
        testBrowserProxy.handler.getCallCount('showParentAccessForTimezone'));
    testBrowserProxy.observerRemote.onParentAccessValidationComplete(
        /*success=*/ true);
    await flushTasks();

    // |resolveMethodDropdown| is disabled when auto detect off.
    assertTrue(resolveMethodDropdown.disabled);

    // Verify elements are enabled.
    assertFalse(timeZoneAutoDetectOn.disabled);
    assertFalse(timeZoneAutoDetectOff.disabled);
    assertFalse(timezoneSelector.disabled);
  });
});