chromium/chrome/test/data/webui/chromeos/settings/date_time_page/date_time_settings_card_test.ts

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

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

import {DateTimeSettingsCardElement, TimeZoneAutoDetectMethod, TimezoneSelectorElement} from 'chrome://os-settings/lazy_load.js';
import {CrLinkRowElement, CrSettingsPrefs, DateTimeBrowserProxy, PrefsState, Router, routes, settingMojom, 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, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';

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

// 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)'],
];

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,
      },
    },
  };
}

suite('<date-time-settings-card>', () => {
  const isRevampWayfindingEnabled =
      loadTimeData.getBoolean('isRevampWayfindingEnabled');
  const route =
      isRevampWayfindingEnabled ? routes.SYSTEM_PREFERENCES : routes.DATETIME;

  let dateTimeSettingsCard: DateTimeSettingsCardElement;
  let testBrowserProxy: TestDateTimeBrowserProxy;

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

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

  function createDateTimeSettingsCard(prefs: PrefsState): void {
    const initialTimezoneDisplayName = prefs['cros'].system.timezone.value;

    // Find the desired initial timezone by ID.
    const timeZone = fakeTimezones.find(
        (timeZonePair) => timeZonePair[0] === initialTimezoneDisplayName);
    assertTrue(!!timeZone);
    const [timeZoneID, timeZoneName] = timeZone;
    loadTimeData.overrideValues({
      timeZoneID,
      timeZoneName,
    });

    dateTimeSettingsCard = document.createElement('date-time-settings-card');
    dateTimeSettingsCard.prefs = prefs;
    dateTimeSettingsCard.activeTimeZoneDisplayName = initialTimezoneDisplayName;
    CrSettingsPrefs.setInitialized();
    document.body.appendChild(dateTimeSettingsCard);
    flush();
  }

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

  function getTimeZoneSelector(): TimezoneSelectorElement {
    const timezoneSelector =
        dateTimeSettingsCard.shadowRoot!.querySelector('timezone-selector');
    assertTrue(!!timezoneSelector);
    return timezoneSelector;
  }

  function getUserTimezoneDropdown(): SettingsDropdownMenuElement {
    const timezoneSelector = getTimeZoneSelector();
    const userTimezoneDropdown =
        timezoneSelector.shadowRoot!.querySelector<SettingsDropdownMenuElement>(
            '#userTimeZoneSelector');
    assertTrue(!!userTimezoneDropdown);
    return userTimezoneDropdown;
  }

  function assertTimezonesPopulated(isPopulated: boolean): void {
    const timezoneDropdown = getUserTimezoneDropdown();
    const numExpected = isPopulated ? fakeTimezones.length : 1;
    assertEquals(numExpected, timezoneDropdown.menuOptions.length);
  }

  test('Set date and time row only shows if editable', async () => {
    const prefs = getFakePrefs();
    createDateTimeSettingsCard(prefs);

    const setDateTimeRow =
        dateTimeSettingsCard.shadowRoot!.querySelector<CrLinkRowElement>(
            '#setDateTimeRow');
    assertFalse(isVisible(setDateTimeRow));

    // Make the date and time editable.
    testBrowserProxy.observerRemote.onSystemClockCanSetTimeChanged(
        /*is_allowed=*/ true);
    await flushTasks();
    assertTrue(isVisible(setDateTimeRow));

    assertEquals(0, testBrowserProxy.handler.getCallCount('showSetDateTimeUI'));
    setDateTimeRow!.click();
    assertEquals(1, testBrowserProxy.handler.getCallCount('showSetDateTimeUI'));

    // Make the date and time not editable.
    testBrowserProxy.observerRemote.onSystemClockCanSetTimeChanged(
        /*is_allowed=*/ false);
    await flushTasks();
    assertFalse(isVisible(setDateTimeRow));
  });

  suite('When timezone detection is enabled', () => {
    setup(() => {
      const prefs = getFakePrefs();
      prefs.cros.flags.fine_grained_time_zone_detection_enabled.value = true;
      createDateTimeSettingsCard(prefs);
    });

    test('Timezone selector is hidden', () => {
      const timezoneSelector =
          dateTimeSettingsCard.shadowRoot!.querySelector('timezone-selector');
      assertFalse(isVisible(timezoneSelector));
    });

    test('Timezone auto-detect toggle is hidden', () => {
      const toggle = dateTimeSettingsCard.shadowRoot!
                         .querySelector<SettingsToggleButtonElement>(
                             '#timeZoneAutoDetectToggle');
      assertFalse(isVisible(toggle));
    });

    test('Timezone subpage row is visible', () => {
      const rowElement =
          dateTimeSettingsCard.shadowRoot!.querySelector<HTMLElement>(
              '#timeZoneSettingsTrigger');
      assertTrue(isVisible(rowElement));
    });

    test(
        'Timezone subpage row is focused after returning from subpage',
        async () => {
          const triggerSelector = '#timeZoneSettingsTrigger';
          const rowElement =
              dateTimeSettingsCard.shadowRoot!.querySelector<HTMLElement>(
                  triggerSelector);
          assertTrue(!!rowElement);
          rowElement.click();
          flush();

          const popStateEventPromise = eventToPromise('popstate', window);
          Router.getInstance().navigateToPreviousRoute();
          await popStateEventPromise;
          await waitAfterNextRender(dateTimeSettingsCard);

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

  suite('When timezone detection is disabled', () => {
    setup(() => {
      const prefs = getFakePrefs();
      prefs.cros.flags.fine_grained_time_zone_detection_enabled.value = false;
      createDateTimeSettingsCard(prefs);
    });

    test('Timezone selector is visible', () => {
      const timezoneSelector =
          dateTimeSettingsCard.shadowRoot!.querySelector('timezone-selector');
      assertTrue(isVisible(timezoneSelector));
    });

    test('Timezone auto-detect toggle is visible', () => {
      const toggle = getTimezoneAutoDetectToggle();
      assertTrue(isVisible(toggle));
    });

    test('Timezone detect toggle is deep linkable', async () => {
      const params = new URLSearchParams();
      const settingId = settingMojom.Setting.kChangeTimeZone.toString();
      params.append('settingId', settingId);
      Router.getInstance().navigateTo(route, params);
      flush();

      const toggle = getTimezoneAutoDetectToggle();
      await waitAfterNextRender(toggle);
      assertEquals(
          toggle, dateTimeSettingsCard.shadowRoot!.activeElement,
          `Auto set time zone toggle should be focused for settingId=${
              settingId}.`);
    });

    test('Turning off timezone auto-detection loads timezones', async () => {
      assertEquals(0, testBrowserProxy.handler.getCallCount('getTimezones'));

      const autodetectToggle = getTimezoneAutoDetectToggle();
      assertTrue(
          autodetectToggle.checked, 'Expected auto-detect toggle to be on');
      assertTimezonesPopulated(false);

      autodetectToggle.click();
      assertFalse(
          autodetectToggle.checked, 'Expected auto-detect toggle to be off');
      assertEquals(1, testBrowserProxy.handler.getCallCount('getTimezones'));
      await flushTasks();

      assertTimezonesPopulated(true);
    });

    test('Auto-detect toggle controls pref', () => {
      const autodetectToggle = getTimezoneAutoDetectToggle();
      assertTrue(
          autodetectToggle.checked, 'Expected auto-detect toggle to be on');
      assertTrue(dateTimeSettingsCard.get(
          'prefs.generated.resolve_timezone_by_geolocation_on_off.value'));

      autodetectToggle.click();
      assertFalse(
          autodetectToggle.checked, 'Expected auto-detect toggle to be off');
      assertFalse(dateTimeSettingsCard.get(
          'prefs.generated.resolve_timezone_by_geolocation_on_off.value'));
    });
  });
});