chromium/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts

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

/** @fileoverview Test suite for theme-element component.  */

import 'chrome://personalization/strings.m.js';

import {emptyState, PersonalizationThemeElement, SetDarkModeEnabledAction, setGeolocationPermissionEnabledAction, SetGeolocationPermissionEnabledActionForTheme, ThemeActionName, ThemeObserver} from 'chrome://personalization/js/personalization_app.js';
import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';

import {baseSetup, initElement} from './personalization_app_test_utils.js';
import {TestPersonalizationStore} from './test_personalization_store.js';
import {TestThemeProvider} from './test_theme_interface_provider.js';

const LIGHT_MODE_BUTTON_ID = 'lightMode';
const DARK_MODE_BUTTON_ID = 'darkMode';
const AUTO_MODE_BUTTON_ID = 'autoMode';

suite('PersonalizationThemeTest', function() {
  let personalizationThemeElement: PersonalizationThemeElement|null;
  let themeProvider: TestThemeProvider;
  let personalizationStore: TestPersonalizationStore;

  setup(() => {
    const mocks = baseSetup();
    themeProvider = mocks.themeProvider;
    personalizationStore = mocks.personalizationStore;
    ThemeObserver.initThemeObserverIfNeeded();
  });

  teardown(async () => {
    if (personalizationThemeElement) {
      personalizationThemeElement.remove();
    }
    personalizationThemeElement = null;
    ThemeObserver.shutdown();
    await flushTasks();
  });

  test('displays content', async () => {
    personalizationStore.data.theme.darkModeEnabled = false;
    personalizationStore.data.theme.colorModeAutoScheduleEnabled = false;
    personalizationThemeElement = initElement(PersonalizationThemeElement);
    await waitAfterNextRender(personalizationThemeElement);

    const radioButton = personalizationThemeElement.shadowRoot!.getElementById(
        DARK_MODE_BUTTON_ID);
    assertTrue(!!radioButton);
  });

  test('sets color mode in store on first load', async () => {
    personalizationStore.expectAction(ThemeActionName.SET_DARK_MODE_ENABLED);
    personalizationThemeElement = initElement(PersonalizationThemeElement);
    const action =
        await personalizationStore.waitForAction(
            ThemeActionName.SET_DARK_MODE_ENABLED) as SetDarkModeEnabledAction;
    assertTrue(action.enabled);
  });

  test('sets theme data in store on changed', async () => {
    // Make sure state starts as expected.
    assertDeepEquals(emptyState(), personalizationStore.data);
    await themeProvider.whenCalled('setThemeObserver');

    personalizationStore.expectAction(ThemeActionName.SET_DARK_MODE_ENABLED);
    themeProvider.themeObserverRemote!.onColorModeChanged(
        /*darkModeEnabled=*/ false);

    const {enabled} =
        await personalizationStore.waitForAction(
            ThemeActionName.SET_DARK_MODE_ENABLED) as SetDarkModeEnabledAction;
    assertFalse(enabled);
  });

  test('shows selected button on load', async () => {
    personalizationThemeElement = initElement(PersonalizationThemeElement);
    personalizationStore.data.theme.darkModeEnabled = true;
    personalizationStore.data.theme.colorModeAutoScheduleEnabled = false;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(personalizationThemeElement);
    const radioButton = personalizationThemeElement.shadowRoot!.getElementById(
        DARK_MODE_BUTTON_ID);
    assertTrue(!!radioButton);
    assertEquals(radioButton.getAttribute('aria-checked'), 'true');
  });

  test('sets dark mode enabled when dark button is clicked', async () => {
    personalizationThemeElement = initElement(PersonalizationThemeElement);
    personalizationStore.data.theme.darkModeEnabled = false;
    personalizationStore.data.theme.colorModeAutoScheduleEnabled = false;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(personalizationThemeElement);
    const radioButton = personalizationThemeElement.shadowRoot!.getElementById(
        DARK_MODE_BUTTON_ID);
    assertTrue(!!radioButton);
    assertEquals(radioButton.getAttribute('aria-checked'), 'false');

    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(ThemeActionName.SET_DARK_MODE_ENABLED);
    radioButton.click();
    const action =
        await personalizationStore.waitForAction(
            ThemeActionName.SET_DARK_MODE_ENABLED) as SetDarkModeEnabledAction;
    assertTrue(action.enabled);
    assertTrue(personalizationStore.data.theme.darkModeEnabled);
    assertEquals(radioButton.getAttribute('aria-checked'), 'true');
  });

  test('sets auto mode enabled when auto button is clicked', async () => {
    personalizationThemeElement = initElement(PersonalizationThemeElement);
    personalizationStore.data.theme.darkModeEnabled = false;
    personalizationStore.data.theme.colorModeAutoScheduleEnabled = false;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(personalizationThemeElement);
    const radioButton = personalizationThemeElement.shadowRoot!.getElementById(
        AUTO_MODE_BUTTON_ID);
    assertTrue(!!radioButton);
    assertEquals(radioButton.getAttribute('aria-checked'), 'false');

    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(
        ThemeActionName.SET_COLOR_MODE_AUTO_SCHEDULE_ENABLED);
    radioButton.click();
    const action = await personalizationStore.waitForAction(
                       ThemeActionName.SET_COLOR_MODE_AUTO_SCHEDULE_ENABLED) as
        SetDarkModeEnabledAction;
    assertTrue(action.enabled);
    assertTrue(personalizationStore.data.theme.colorModeAutoScheduleEnabled);
    assertEquals(radioButton.getAttribute('aria-checked'), 'true');

    // reclicking the button does not disable auto mode.
    radioButton.click();
    assertEquals(radioButton.getAttribute('aria-checked'), 'true');
  });

  async function setThemeColorMode(colorModeButtonId: string) {
    const colorModeButton: CrButtonElement =
        personalizationThemeElement!.shadowRoot!.getElementById(
            colorModeButtonId)! as CrButtonElement;
    colorModeButton.click();
    flushTasks();
    await waitAfterNextRender(personalizationThemeElement!);
  }

  function isAutoModeLocationWarningIconPresent(): boolean {
    return !!personalizationThemeElement!.shadowRoot!.getElementById(
        'locationDeniedInfoIcon');
  }

  async function setGeolocationPermissionEnabled(enabled: boolean) {
    personalizationStore.expectAction(
        ThemeActionName.SET_GEOLOCATION_PERMISSION_ENABLED);
    personalizationStore.dispatch(
        setGeolocationPermissionEnabledAction(enabled));
    const action = await personalizationStore.waitForAction(
                       ThemeActionName.SET_GEOLOCATION_PERMISSION_ENABLED) as
        SetGeolocationPermissionEnabledActionForTheme;
    if (enabled) {
      assertTrue(action.enabled);

    } else {
      assertFalse(action.enabled);
    }

    personalizationStore.notifyObservers();
  }

  test(
      'geolocation warning tooltip should be hidden when PrivacyHub disabled',
      async () => {
        // Disable Privacy Hub feature flag.
        loadTimeData.overrideValues({isCrosPrivacyHubLocationEnabled: false});

        personalizationThemeElement = initElement(PersonalizationThemeElement);
        personalizationStore.setReducersEnabled(true);

        // Check that geolocation content is not displayed on any configuration.
        setThemeColorMode(LIGHT_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when PH is disabled');
        setThemeColorMode(DARK_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when PH is disabled');
        setThemeColorMode(AUTO_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when PH is disabled');
      });


  test(
      'shows geolocation warning tooltip on location disabled ' +
          '(PrivacyHub enabled)',
      async () => {
        // Enable Privacy Hub feature flag.
        loadTimeData.overrideValues({isCrosPrivacyHubLocationEnabled: true});

        personalizationThemeElement = initElement(PersonalizationThemeElement);
        personalizationStore.setReducersEnabled(true);

        // Enable geolocation.
        setGeolocationPermissionEnabled(true);
        // Check that tooltip is not shown on any configuration.
        setThemeColorMode(LIGHT_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when system geolocation is enabled');
        setThemeColorMode(DARK_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when system geolocation is enabled');
        setThemeColorMode(AUTO_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when system geolocation is enabled');

        // Disable geolocation.
        setGeolocationPermissionEnabled(false);
        // Check that tooltip is only shown when Auto Schedule is selected.
        setThemeColorMode(LIGHT_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when AutoMode not selected');
        setThemeColorMode(DARK_MODE_BUTTON_ID);
        assertFalse(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip shown when AutoMode not selected');
        setThemeColorMode(AUTO_MODE_BUTTON_ID);
        assertTrue(
            isAutoModeLocationWarningIconPresent(),
            'Tooltip not shown when AutoMode is selected');
      });

  function isGeolocationDialogVisible(): boolean {
    return !!personalizationThemeElement!.shadowRoot!.getElementById(
        'geolocationDialog');
  }

  test('show Geolocation dialog and click allow', async () => {
    personalizationThemeElement = initElement(PersonalizationThemeElement);
    personalizationStore.setReducersEnabled(true);

    // Enable Privacy Hub feature flag.
    loadTimeData.overrideValues({isCrosPrivacyHubLocationEnabled: true});

    // Set the default sunrise/sunset time.
    personalizationStore.data.theme.sunriseTime = '6:00AM';
    personalizationStore.data.theme.sunsetTime = '6:00PM';

    // Disable geolocation and select Auto Schedule; This should show the
    // warning tooltip.
    setGeolocationPermissionEnabled(false);
    setThemeColorMode(AUTO_MODE_BUTTON_ID);
    assertTrue(isAutoModeLocationWarningIconPresent());

    // Check that the dialog has popped up.
    assertTrue(isGeolocationDialogVisible());

    // Check that the dialog mentions the sunset/sunrise times.
    const geolocationDialog =
        personalizationThemeElement.shadowRoot!.getElementById(
            'geolocationDialog')!;
    const dialogBodyText =
        geolocationDialog.shadowRoot!
            .querySelector<HTMLDivElement>('#dialogBody')!.innerText;
    assertTrue(
        dialogBodyText.includes('6:00AM-6:00PM'),
        'dialog body doesn\'t include sunrise/sunset times');

    // Confirm the dialog; this should enable the geolocation permission,
    // resulting in both the dialog and warning tooltip disappearing.
    const confirmButton =
        geolocationDialog.shadowRoot!.getElementById('confirmButton');
    assertTrue(!!confirmButton);
    personalizationStore.expectAction(
        ThemeActionName.SET_GEOLOCATION_PERMISSION_ENABLED);
    confirmButton.click();
    const action = await personalizationStore.waitForAction(
                       ThemeActionName.SET_GEOLOCATION_PERMISSION_ENABLED) as
        SetGeolocationPermissionEnabledActionForTheme;
    assertTrue(action.enabled);
    personalizationStore.notifyObservers();
    await waitAfterNextRender(personalizationThemeElement);

    // Check that both warning tooltip and dialog has diappeared.
    assertFalse(isAutoModeLocationWarningIconPresent());
    assertFalse(isGeolocationDialogVisible());
  });

  test('iron-selector excludes geolocation warning', async () => {
    // Enable Privacy Hub feature flag.
    loadTimeData.overrideValues({isCrosPrivacyHubLocationEnabled: true});
    personalizationStore.data.theme.geolocationPermissionEnabled = false;
    personalizationStore.data.theme.colorModeAutoScheduleEnabled = true;

    personalizationThemeElement = initElement(PersonalizationThemeElement);
    await waitAfterNextRender(personalizationThemeElement!);

    assertEquals(
        'true',
        personalizationThemeElement.shadowRoot?.getElementById('autoMode')
            ?.ariaChecked,
        'auto mode button is checked');
    assertTrue(
        isAutoModeLocationWarningIconPresent(),
        'location warning icon is present');

    assertDeepEquals(
        ['lightMode', 'darkMode', 'autoMode'],
        personalizationThemeElement.$.selector.items?.map(item => item.id),
        'only theme buttons are selectable by iron-selector');
  });
});