chromium/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_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.

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

import {AlbumsSubpageElement, AmbientActionName, AmbientModeAlbum, AmbientObserver, AmbientSubpageElement, AmbientTheme, AmbientThemeItemElement, AmbientUiVisibility, emptyState, Paths, PersonalizationRouterElement, QueryParams, ScrollableTarget, SetAlbumsAction, SetAmbientModeEnabledAction, SetAmbientThemeAction, SetGeolocationPermissionEnabledActionForAmbient, SetScreenSaverDurationAction, SetTemperatureUnitAction, SetTopicSourceAction, TemperatureUnit, TopicSource, TopicSourceItemElement, WallpaperGridItemElement} from 'chrome://personalization/js/personalization_app.js';
import {CrRadioButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_radio_button/cr_radio_button.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {TestMock} from 'chrome://webui-test/test_mock.js';

import {baseSetup, initElement, teardownElement} from './personalization_app_test_utils.js';
import {TestAmbientProvider} from './test_ambient_interface_provider.js';
import {TestPersonalizationStore} from './test_personalization_store.js';


export function getSelectedAlbums(
    albums: AmbientModeAlbum[], topicSource: TopicSource): AmbientModeAlbum[] {
  return albums.filter(
      album => album.topicSource === topicSource && album.checked);
}

suite('AmbientSubpageElementTest', function() {
  let ambientSubpageElement: AmbientSubpageElement|null;
  let ambientProvider: TestAmbientProvider;
  let personalizationStore: TestPersonalizationStore;
  const routerOriginal = PersonalizationRouterElement.instance;
  const routerMock = TestMock.fromClass(PersonalizationRouterElement);

  const enum DurationOptions {
    FIVE_MINUTES = '5',
    TEN_MINUTES = '10',
    THIRTY_MINUTES = '30',
    ONE_HOUR = '60',
    FOREVER = '0',
  }

  setup(() => {
    loadTimeData.overrideValues({isAmbientModeAllowed: true});
    const mocks = baseSetup();
    ambientProvider = mocks.ambientProvider;
    personalizationStore = mocks.personalizationStore;
    AmbientObserver.initAmbientObserverIfNeeded();
    PersonalizationRouterElement.instance = () => routerMock;
  });

  teardown(async () => {
    await teardownElement(ambientSubpageElement);
    ambientSubpageElement = null;
    AmbientObserver.shutdown();
    PersonalizationRouterElement.instance = routerOriginal;
  });

  async function displayMainSettings(
      topicSource: TopicSource|null, temperatureUnit: TemperatureUnit|null,
      ambientModeEnabled: boolean|null, ambientTheme = AmbientTheme.kSlideshow,
      previews: Url[] = [], duration: number|null = 10,
      queryParams: QueryParams = {}): Promise<AmbientSubpageElement> {
    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.data.ambient.ambientTheme = ambientTheme;
    personalizationStore.data.ambient.topicSource = topicSource;
    personalizationStore.data.ambient.temperatureUnit = temperatureUnit;
    personalizationStore.data.ambient.ambientModeEnabled = ambientModeEnabled;
    personalizationStore.data.ambient.previews = previews;
    personalizationStore.data.ambient.duration = duration;
    const ambientSubpage =
        initElement(AmbientSubpageElement, {path: Paths.AMBIENT, queryParams});
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpage);
    return Promise.resolve(ambientSubpage);
  }

  function selectDropDownMenuOption(select: HTMLSelectElement, value: string) {
    select.value = value;
    select.dispatchEvent(new CustomEvent('change'));
    flush();
  }

  test('displays content', async () => {
    ambientSubpageElement = await displayMainSettings(
        /*topicSource=*/ null, /*temperatureUnit=*/ null,
        /*ambientModeEnabled=*/ null);
    // Shows placeholder for ambient mode toggle row while loading ambient mode
    // status.
    const toggleRowPlaceholder =
        ambientSubpageElement.shadowRoot!.querySelector(
            '#toggleRowPlaceholder');
    assertTrue(!!toggleRowPlaceholder);

    // Preview element should show placeholders for preview images, preview
    // album info and preview album collage.
    const ambientPreview = ambientSubpageElement.shadowRoot!.querySelector(
        'ambient-preview-small');
    assertTrue(!!ambientPreview, 'ambient-preview element exists');

    // Should show image placeholders for the 3 theme items.
    const ambientThemePlaceholder =
        ambientSubpageElement.shadowRoot!.querySelector(
            '#ambientThemePlaceholder');
    assertTrue(!!ambientThemePlaceholder);

    const ambientThemeItemPlaceholders =
        ambientSubpageElement!.shadowRoot!.querySelectorAll(
            '.ambient-theme-placeholder-container:not([hidden])');
    assertEquals(3, ambientThemeItemPlaceholders!.length);

    // Should show placeholders for the 2 topic source radio buttons.
    const topicSourcePlaceholder =
        ambientSubpageElement.shadowRoot!.querySelector(
            '#topicSourcePlaceholder');
    assertTrue(!!topicSourcePlaceholder);

    const topicSourceItemPlaceholders =
        ambientSubpageElement.shadowRoot!.querySelectorAll(
            '.topic-source-placeholder:not([hidden])');
    assertEquals(2, topicSourceItemPlaceholders!.length);

    // Should show placeholders for 2 weather unit radio buttons.
    const weatherUnitPlaceholder =
        ambientSubpageElement.shadowRoot!.querySelector(
            '#weatherUnitPlaceholder');
    assertTrue(!!weatherUnitPlaceholder);

    const weatherUnitItemPlaceholders =
        ambientSubpageElement.shadowRoot!.querySelectorAll(
            '.weather-unit-placeholder:not([hidden])');
    assertEquals(2, weatherUnitItemPlaceholders!.length);

    personalizationStore.data.ambient.ambientModeEnabled = false;
    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.data.ambient.topicSource = TopicSource.kGooglePhotos;
    personalizationStore.data.ambient.temperatureUnit =
        TemperatureUnit.kFahrenheit;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    // Loading finished, should not show toggle row placeholder.
    assertTrue(!!toggleRowPlaceholder);
    assertEquals(getComputedStyle(toggleRowPlaceholder).display, 'none');

    // Toggle row is shown but in off status (the button is unchecked).
    const toggleRow =
        ambientSubpageElement.shadowRoot!.querySelector('toggle-row');
    assertTrue(!!toggleRow, 'toggle-row element exists');
    const toggleButton = toggleRow!.shadowRoot!.querySelector('cr-toggle');
    assertTrue(!!toggleButton, 'cr-toggle element exists');
    assertFalse(toggleButton!.checked);

    // Placeholders will be hidden for ambient theme, topic source
    // and temperature unit elements.
    assertTrue(!!ambientThemePlaceholder);
    assertEquals(
        'none', getComputedStyle(ambientThemePlaceholder).display,
        'ambient theme placeholder is hidden');

    assertTrue(!!topicSourcePlaceholder);
    assertEquals(
        'none', getComputedStyle(topicSourcePlaceholder).display,
        'topic source placeholder is hidden');

    assertTrue(!!weatherUnitPlaceholder);
    assertEquals(
        'none', getComputedStyle(weatherUnitPlaceholder).display,
        'weather unit placeholder is hidden');
  });

  test('sets ambient mode enabled in store on first load', async () => {
    personalizationStore.expectAction(
        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ false);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action = await personalizationStore.waitForAction(
                       AmbientActionName.SET_AMBIENT_MODE_ENABLED) as
        SetAmbientModeEnabledAction;
    assertTrue(action.enabled);
  });

  test('sets loading when there is no network', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    // Simulate going offline.
    window.dispatchEvent(new CustomEvent('offline'));
    await waitAfterNextRender(ambientSubpageElement);

    // Shows placeholder for ambient mode toggle row while loading ambient mode
    // status.
    const toggleRowPlaceholder =
        ambientSubpageElement.shadowRoot!.querySelector(
            '#toggleRowPlaceholder');
    assertTrue(!!toggleRowPlaceholder);
  });

  test('sets ambient mode when pref value changed', async () => {
    // Make sure state starts as expected.
    assertDeepEquals(emptyState(), personalizationStore.data);
    ambientSubpageElement = initElement(AmbientSubpageElement);
    await ambientProvider.whenCalled('setAmbientObserver');

    personalizationStore.expectAction(
        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
    ambientProvider.ambientObserverRemote!.onAmbientModeEnabledChanged(
        /*ambientModeEnabled=*/ false);

    const action = await personalizationStore.waitForAction(
                       AmbientActionName.SET_AMBIENT_MODE_ENABLED) as
        SetAmbientModeEnabledAction;
    assertFalse(action.enabled);
  });

  test('sets ambient mode enabled when toggle row clicked', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const toggleRow =
        ambientSubpageElement.shadowRoot!.querySelector('toggle-row');
    assertTrue(!!toggleRow);
    const toggleButton = toggleRow!.shadowRoot!.querySelector('cr-toggle');
    assertTrue(!!toggleButton);
    assertTrue(toggleButton!.checked);

    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(
        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
    toggleButton.click();
    let action = await personalizationStore.waitForAction(
                     AmbientActionName.SET_AMBIENT_MODE_ENABLED) as
        SetAmbientModeEnabledAction;
    assertFalse(action.enabled);
    assertFalse(!!personalizationStore.data.ambient.ambientModeEnabled);
    assertFalse(toggleButton!.checked);

    personalizationStore.expectAction(
        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
    toggleButton.click();
    action = await personalizationStore.waitForAction(
                 AmbientActionName.SET_AMBIENT_MODE_ENABLED) as
        SetAmbientModeEnabledAction;
    assertTrue(action.enabled);
    assertTrue(!!personalizationStore.data.ambient.ambientModeEnabled);
    assertTrue(toggleButton!.checked);
  });

  test('sets ambient mode enabled when toggle button clicked', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const toggleRow =
        ambientSubpageElement.shadowRoot!.querySelector('toggle-row');
    assertTrue(!!toggleRow);
    const toggleButton = toggleRow!.shadowRoot!.querySelector('cr-toggle');
    assertTrue(!!toggleButton);
    assertTrue(toggleButton!.checked);

    personalizationStore.expectAction(
        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
    toggleButton.click();
    let action = await personalizationStore.waitForAction(
                     AmbientActionName.SET_AMBIENT_MODE_ENABLED) as
        SetAmbientModeEnabledAction;
    assertFalse(action.enabled);

    personalizationStore.expectAction(
        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
    toggleButton.click();
    action = await personalizationStore.waitForAction(
                 AmbientActionName.SET_AMBIENT_MODE_ENABLED) as
        SetAmbientModeEnabledAction;
    assertTrue(action.enabled);
  });

  test('has correct ambient theme on load', async () => {
    personalizationStore.expectAction(AmbientActionName.SET_AMBIENT_THEME);
    ambientSubpageElement = initElement(AmbientSubpageElement);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_AMBIENT_THEME) as SetAmbientThemeAction;
    assertEquals(AmbientTheme.kSlideshow, action.ambientTheme);
  });

  test('sets ambient theme when ambient theme item is clicked', async () => {
    // See "shows video ambient theme on supported devices" for expected
    // behavior when `isTimeOfDayScreenSaverEnabled` is true.
    loadTimeData.overrideValues({'isTimeOfDayScreenSaverEnabled': false});
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const ambientThemeList =
        ambientSubpageElement.shadowRoot!.querySelector('ambient-theme-list');
    assertTrue(!!ambientThemeList);
    const AmbientThemeItems = ambientThemeList!.shadowRoot!.querySelectorAll(
        'ambient-theme-item:not([hidden])');
    assertEquals(3, AmbientThemeItems!.length);
    const slideshow = AmbientThemeItems[0] as AmbientThemeItemElement;
    const feelTheBreeze = AmbientThemeItems[1] as AmbientThemeItemElement;
    assertEquals(AmbientTheme.kSlideshow, slideshow.ambientTheme);
    assertEquals(AmbientTheme.kFeelTheBreeze, feelTheBreeze.ambientTheme);

    assertEquals(feelTheBreeze.ariaChecked, 'false');
    assertEquals(slideshow.ariaChecked, 'true');

    personalizationStore.expectAction(AmbientActionName.SET_AMBIENT_THEME);
    feelTheBreeze!.click();
    let action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_AMBIENT_THEME) as SetAmbientThemeAction;
    assertEquals(AmbientTheme.kFeelTheBreeze, action.ambientTheme);

    personalizationStore.expectAction(AmbientActionName.SET_AMBIENT_THEME);
    slideshow!.click();
    action = await personalizationStore.waitForAction(
                 AmbientActionName.SET_AMBIENT_THEME) as SetAmbientThemeAction;
    assertEquals(AmbientTheme.kSlideshow, action.ambientTheme);
  });

  test('has correct topic sources on load', async () => {
    personalizationStore.expectAction(AmbientActionName.SET_TOPIC_SOURCE);
    ambientSubpageElement = initElement(AmbientSubpageElement);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_TOPIC_SOURCE) as SetTopicSourceAction;
    assertEquals(TopicSource.kArtGallery, action.topicSource);
  });

  test('sets topic source when topic source item clicked', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const topicSourceList =
        ambientSubpageElement.shadowRoot!.querySelector('topic-source-list');
    assertTrue(!!topicSourceList);
    const topicSourceItems =
        topicSourceList!.shadowRoot!.querySelectorAll('topic-source-item');
    assertEquals(2, topicSourceItems!.length);
    const googlePhotos = topicSourceItems[0] as TopicSourceItemElement;
    const art = topicSourceItems[1] as TopicSourceItemElement;
    assertEquals(TopicSource.kGooglePhotos, googlePhotos.topicSource);
    assertEquals(TopicSource.kArtGallery, art.topicSource);

    assertFalse(googlePhotos.checked);
    assertTrue(art.checked);

    personalizationStore.expectAction(AmbientActionName.SET_TOPIC_SOURCE);
    googlePhotos!.click();
    let action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_TOPIC_SOURCE) as SetTopicSourceAction;
    assertEquals(TopicSource.kGooglePhotos, action.topicSource);

    personalizationStore.expectAction(AmbientActionName.SET_TOPIC_SOURCE);
    art!.click();
    action = await personalizationStore.waitForAction(
                 AmbientActionName.SET_TOPIC_SOURCE) as SetTopicSourceAction;
    assertEquals(TopicSource.kArtGallery, action.topicSource);
  });

  test('has correct temperature unit on load', async () => {
    personalizationStore.expectAction(AmbientActionName.SET_TEMPERATURE_UNIT);
    ambientSubpageElement = initElement(AmbientSubpageElement);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_TEMPERATURE_UNIT) as SetTemperatureUnitAction;
    assertEquals(TemperatureUnit.kFahrenheit, action.temperatureUnit);
  });

  test('sets temperature unit when temperature unit item clicked', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const weatherUnit =
        ambientSubpageElement.shadowRoot!.querySelector('ambient-weather-unit');
    assertTrue(!!weatherUnit);

    const temperatureUnitItems =
        weatherUnit!.shadowRoot!.querySelectorAll<CrRadioButtonElement>(
            'cr-radio-button');
    assertEquals(2, temperatureUnitItems!.length);

    const [fahrenheitUnitButton, celsiusUnitButton] = temperatureUnitItems;

    assertTrue(
        fahrenheitUnitButton!.checked, 'fahrenheit button starts checked');

    personalizationStore.expectAction(AmbientActionName.SET_TEMPERATURE_UNIT);
    celsiusUnitButton!.click();
    assertTrue(celsiusUnitButton!.checked);
    let action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_TEMPERATURE_UNIT) as SetTemperatureUnitAction;
    assertEquals(TemperatureUnit.kCelsius, action.temperatureUnit);

    personalizationStore.expectAction(AmbientActionName.SET_TEMPERATURE_UNIT);
    fahrenheitUnitButton!.click();
    assertTrue(fahrenheitUnitButton!.checked);
    action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_TEMPERATURE_UNIT) as SetTemperatureUnitAction;
    assertEquals(TemperatureUnit.kFahrenheit, action.temperatureUnit);
  });

  test(
      'show warning in temperature section when system geolocation disabled',
      async () => {
        ambientSubpageElement = await displayMainSettings(
            TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
            /*ambientModeEnabled=*/ true);

        // Disable Privacy Hub feature flag. Geolocation content should not be
        // shown.
        loadTimeData.overrideValues({isCrosPrivacyHubLocationEnabled: false});

        const weatherUnit = ambientSubpageElement.shadowRoot!.querySelector(
            'ambient-weather-unit');
        assertTrue(!!weatherUnit);

        // Check that temperature radio buttons are shown, no matter the
        // geolocation permission value.
        for (const geolocationEnabled of [true, false]) {
          personalizationStore.data.ambient.geolocationPermissionEnabled =
              geolocationEnabled;
          personalizationStore.notifyObservers();
          await waitAfterNextRender(ambientSubpageElement);

          const temperatureUnitItems =
              weatherUnit!.shadowRoot!.querySelectorAll<CrRadioButtonElement>(
                  'cr-radio-button');
          assertEquals(2, temperatureUnitItems!.length);

          const geolocationWarningDiv =
              weatherUnit!.shadowRoot!.getElementById('geolocationWarningDiv');
          assertFalse(!!geolocationWarningDiv);
        }
        // Enable Privacy Hub feature flag.
        loadTimeData.overrideValues({isCrosPrivacyHubLocationEnabled: true});

        for (const geolocationEnabled of [true, false]) {
          personalizationStore.data.ambient.geolocationPermissionEnabled =
              geolocationEnabled;
          personalizationStore.notifyObservers();
          await waitAfterNextRender(ambientSubpageElement);

          const temperatureUnitItems =
              weatherUnit!.shadowRoot!.querySelectorAll<CrRadioButtonElement>(
                  'cr-radio-button');
          const geolocationWarningDiv =
              weatherUnit!.shadowRoot!.getElementById('geolocationWarningDiv');

          // Geolocation warning div should only be shown when the geolocation
          // permission is disabled.
          if (geolocationEnabled) {
            assertEquals(2, temperatureUnitItems!.length);
            assertFalse(!!geolocationWarningDiv);
          } else {
            assertEquals(0, temperatureUnitItems!.length);
            assertTrue(!!geolocationWarningDiv);
          }
        }
      });

  test('show Geolocation dialog and click allow', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const weatherUnit =
        ambientSubpageElement.shadowRoot!.querySelector('ambient-weather-unit');
    assertTrue(!!weatherUnit);

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

    // Disable geolocation and select Auto Schedule; This should show the
    // warning message.
    personalizationStore.data.ambient.geolocationPermissionEnabled = false;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    // Check warning message is present.
    let warningElement =
        weatherUnit!.shadowRoot!.getElementById('geolocationWarningDiv');
    assertTrue(!!warningElement);

    // Click the anchor to display the geolocation dialog.
    const localizedLink = warningElement.querySelector('localized-link');
    assertTrue(!!localizedLink);
    const testDetail = {event: {preventDefault: () => {}}};
    localizedLink.dispatchEvent(
        new CustomEvent('link-clicked', {bubbles: false, detail: testDetail}));
    flush();
    await waitAfterNextRender(ambientSubpageElement);

    // Check dialog has popped up.
    let geolocationDialog =
        weatherUnit.shadowRoot!.getElementById('geolocationDialog');
    assertTrue(!!geolocationDialog);
    const confirmButton =
        geolocationDialog.shadowRoot!.getElementById('confirmButton');
    assertTrue(!!confirmButton);

    // Confirm the dialog; this should enable the geolocation permission,
    // resulting in both the dialog and warning text disappearing.
    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(
        AmbientActionName.SET_GEOLOCATION_PERMISSION_ENABLED);
    confirmButton.click();
    const action = await personalizationStore.waitForAction(
                       AmbientActionName.SET_GEOLOCATION_PERMISSION_ENABLED) as
        SetGeolocationPermissionEnabledActionForAmbient;

    // Check the geolocation permission value has updated.
    assertTrue(action.enabled);
    assertTrue(personalizationStore.data.ambient.geolocationPermissionEnabled);

    // Check that both warning text and dialog has diappeared.
    await waitAfterNextRender(ambientSubpageElement);
    warningElement =
        weatherUnit.shadowRoot!.getElementById('geolocationWarningDiv');
    geolocationDialog =
        weatherUnit.shadowRoot!.getElementById('geolocationDialog');
    assertFalse(!!warningElement);
    assertFalse(!!geolocationDialog);
  });

  test('duration is default to ten minutes', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const durationElement =
        ambientSubpageElement.shadowRoot!.querySelector('ambient-duration');
    assertTrue(!!durationElement, 'Duration setting should be renderered');

    const durationOptions =
        durationElement!.shadowRoot!.querySelectorAll<HTMLOptionElement>(
            'option');
    assertEquals(
        5, durationOptions!.length, 'Duration should have exactly 5 options');

    const optionTenMin = durationOptions[1];
    assertTrue(
        optionTenMin!.selected, 'Ten minutes option is initially selected');
  });

  test('sets duration when a new duration option is selected', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const durationElement =
        ambientSubpageElement.shadowRoot!.querySelector<HTMLSelectElement>(
            'ambient-duration');
    assertTrue(!!durationElement, 'Duration setting should be renderered');

    const durationMenu =
        durationElement!.shadowRoot!.querySelector<HTMLSelectElement>(
            '#durationOptions');
    assertTrue(!!durationMenu, 'Duration drop-down menu should be renderered');

    const durationOptions =
        durationElement!.shadowRoot!.querySelectorAll<HTMLOptionElement>(
            'option');
    const optionFiveMin = durationOptions[0];
    const optionForever = durationOptions[durationOptions.length - 1];

    personalizationStore.expectAction(
        AmbientActionName.SET_SCREEN_SAVER_DURATION);
    selectDropDownMenuOption(durationMenu, DurationOptions.FIVE_MINUTES);
    await waitAfterNextRender(ambientSubpageElement);
    assertTrue(optionFiveMin!.selected, 'Five minutes option is selected');
    let action = await personalizationStore.waitForAction(
                     AmbientActionName.SET_SCREEN_SAVER_DURATION) as
        SetScreenSaverDurationAction;
    assertEquals(5, action.minutes, 'Duration should be set to five minutes');

    personalizationStore.expectAction(
        AmbientActionName.SET_SCREEN_SAVER_DURATION);
    selectDropDownMenuOption(durationMenu, DurationOptions.FOREVER);
    await waitAfterNextRender(ambientSubpageElement);
    assertTrue(optionForever!.selected, 'Forever option is selected');
    action = await personalizationStore.waitForAction(
                 AmbientActionName.SET_SCREEN_SAVER_DURATION) as
        SetScreenSaverDurationAction;
    assertEquals(0, action.minutes, 'Duration should be set to forever');
  });

  test('has main settings visible with path ambient', async () => {
    ambientSubpageElement = initElement(
        AmbientSubpageElement, {path: Paths.AMBIENT, queryParams: {}});
    await waitAfterNextRender(ambientSubpageElement);

    const mainSettings =
        ambientSubpageElement.shadowRoot!.querySelector<HTMLElement>(
            '#mainSettings');
    assertTrue(!!mainSettings);
    assertFalse(mainSettings.hidden);

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertFalse(!!albumsSubpage);
  });

  test('scroll to image source when clicked from thumbnail', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit, true,
        AmbientTheme.kSlideshow, [], 10,
        {scrollTo: ScrollableTarget.TOPIC_SOURCE_LIST});

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const imageSource =
        ambientSubpageElement.shadowRoot!.querySelector('topic-source-list');
    assertTrue(!!imageSource, 'Image source should present.');
  });

  test('has albums subpage visible with path ambient albums', async () => {
    ambientSubpageElement = initElement(AmbientSubpageElement, {
      path: Paths.AMBIENT_ALBUMS,
      queryParams: {topicSource: TopicSource.kArtGallery},
    });
    personalizationStore.data.ambient.ambientModeEnabled = true;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    const mainSettings =
        ambientSubpageElement.shadowRoot!.querySelector<HTMLElement>(
            '#mainSettings');
    assertFalse(!!mainSettings);

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertTrue(!!albumsSubpage);
    assertFalse(albumsSubpage.hidden);
  });

  test(
      'loading albums subpage redirects to ambient subpage if disabled',
      async () => {
        const reloadCalledPromise = new Promise<void>((resolve) => {
          PersonalizationRouterElement.reloadAtAmbient = resolve;
        });
        const albumsSubpageElement = initElement(AlbumsSubpageElement);
        personalizationStore.data.ambient.ambientModeEnabled = false;
        personalizationStore.notifyObservers();
        await waitAfterNextRender(albumsSubpageElement);

        await reloadCalledPromise;
      });

  test('show placeholders when no albums on albums subpage', async () => {
    ambientSubpageElement = initElement(AmbientSubpageElement, {
      path: Paths.AMBIENT_ALBUMS,
      queryParams: {topicSource: TopicSource.kGooglePhotos},
    });
    personalizationStore.data.ambient.ambientModeEnabled = true;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertTrue(!!albumsSubpage);
    assertFalse(albumsSubpage.hidden);
    await waitAfterNextRender(albumsSubpage);

    const descPlaceholder =
        albumsSubpage.shadowRoot!.querySelector('#descPlaceholderContainer');
    assertTrue(!!descPlaceholder);
    assertNotEquals(getComputedStyle(descPlaceholder).display, 'none');

    const albumsPlaceholder =
        albumsSubpage.shadowRoot!.querySelector('#albumsPlaceholderContainer');
    assertTrue(!!albumsPlaceholder);
    assertNotEquals(getComputedStyle(albumsPlaceholder).display, 'none');

    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.data.ambient.topicSource = TopicSource.kGooglePhotos;
    personalizationStore.data.ambient.temperatureUnit =
        TemperatureUnit.kFahrenheit;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(albumsSubpage);

    assertTrue(!!descPlaceholder);
    assertEquals(getComputedStyle(descPlaceholder).display, 'none');

    assertTrue(!!albumsPlaceholder);
    assertEquals(getComputedStyle(albumsPlaceholder).display, 'none');
  });

  test('has correct albums on Google Photos albums subpage', async () => {
    ambientSubpageElement = initElement(AmbientSubpageElement, {
      path: Paths.AMBIENT_ALBUMS,
      queryParams: {topicSource: TopicSource.kGooglePhotos},
    });
    personalizationStore.data.ambient.ambientModeEnabled = true;
    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertTrue(!!albumsSubpage);
    assertFalse(albumsSubpage.hidden);
    await waitAfterNextRender(albumsSubpage);

    const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
    assertTrue(!!albumList);

    const albums =
        albumList.shadowRoot!.querySelectorAll<WallpaperGridItemElement>(
            'wallpaper-grid-item:not([hidden])');
    assertEquals(1, albums.length);
  });

  test('has correct albums on Art albums subpage', async () => {
    ambientSubpageElement = initElement(AmbientSubpageElement, {
      path: Paths.AMBIENT_ALBUMS,
      queryParams: {topicSource: TopicSource.kArtGallery},
    });
    personalizationStore.data.ambient.ambientModeEnabled = true;
    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertTrue(!!albumsSubpage);
    assertFalse(albumsSubpage.hidden);
    await waitAfterNextRender(albumsSubpage);

    const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
    assertTrue(!!albumList);

    const albums =
        albumList.shadowRoot!.querySelectorAll<WallpaperGridItemElement>(
            'wallpaper-grid-item:not([hidden])');
    assertEquals(3, albums.length);
    assertTrue(!!albums[0]);
    assertTrue(!!albums[1]);
    assertTrue(!!albums[2]);
  });

  test('toggle album selection by clicking', async () => {
    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(AmbientActionName.SET_ALBUMS);
    ambientSubpageElement = initElement(AmbientSubpageElement, {
      path: Paths.AMBIENT_ALBUMS,
      queryParams: {topicSource: TopicSource.kArtGallery},
    });
    personalizationStore.data.ambient.ambientModeEnabled = true;
    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action = await personalizationStore.waitForAction(
                       AmbientActionName.SET_ALBUMS) as SetAlbumsAction;
    assertEquals(6, action.albums.length, 'action.albums.length');

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertTrue(!!albumsSubpage, '!!albumsSubpage');
    assertFalse(albumsSubpage.hidden, 'albumsSubpage.hidden');
    await waitAfterNextRender(albumsSubpage);

    const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
    assertTrue(!!albumList, '!!albumList');

    const albums =
        albumList.shadowRoot!.querySelectorAll<WallpaperGridItemElement>(
            'wallpaper-grid-item:not([hidden])');
    assertEquals(3, albums.length);
    assertTrue(!!albums[0], '!!albums[0]');
    assertTrue(!!albums[1], '!!albums[1]');
    assertTrue(!!albums[2], '!!albums[2]');
    assertFalse(albums[0].selected!, 'albums[0].selected!');
    assertFalse(albums[1].selected!, 'albums[1].selected!');
    assertTrue(albums[2].selected!, 'albums[2].selected!');
    let selectedAlbums = getSelectedAlbums(
        personalizationStore.data.ambient.albums || [],
        personalizationStore.data.ambient.topicSource!);
    assertEquals(1, selectedAlbums!.length);
    assertEquals('2', selectedAlbums[0]!.title);

    personalizationStore.expectAction(AmbientActionName.SET_ALBUM_SELECTED);
    albums[1].click();
    assertTrue(albums[1].selected!, 'albums[1].selected!');
    await personalizationStore.waitForAction(
        AmbientActionName.SET_ALBUM_SELECTED);
    selectedAlbums = getSelectedAlbums(
        personalizationStore.data.ambient.albums || [],
        personalizationStore.data.ambient.topicSource!);
    assertEquals(2, selectedAlbums!.length);
    assertEquals('1', selectedAlbums[0]!.title);
    assertEquals('2', selectedAlbums[1]!.title);
  });

  test('not deselect last art album', async () => {
    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(AmbientActionName.SET_ALBUMS);
    ambientSubpageElement = initElement(AmbientSubpageElement, {
      path: Paths.AMBIENT_ALBUMS,
      queryParams: {topicSource: TopicSource.kArtGallery},
    });
    personalizationStore.data.ambient.ambientModeEnabled = true;
    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action = await personalizationStore.waitForAction(
                       AmbientActionName.SET_ALBUMS) as SetAlbumsAction;
    assertEquals(6, action.albums.length);

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertTrue(!!albumsSubpage);
    assertFalse(albumsSubpage.hidden);
    await waitAfterNextRender(albumsSubpage);

    const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
    assertTrue(!!albumList);

    const albums =
        albumList.shadowRoot!.querySelectorAll<WallpaperGridItemElement>(
            'wallpaper-grid-item:not([hidden])');
    assertEquals(3, albums.length);
    assertTrue(!!albums[0], '!!albums[0]');
    assertTrue(!!albums[1], '!!albums[1]');
    assertTrue(!!albums[2], '!!albums[2]');
    assertFalse(albums[0].selected!, 'albums[0].selected!');
    assertFalse(albums[1].selected!, 'albums[1].selected!');
    assertTrue(albums[2].selected!, 'albums[2].selected!');

    // Click the last art album item image will not toggle the check and will
    // show a dialog.
    albums[2].click();
    assertTrue(albums[2].selected, 'albums[2].selected');

    await waitAfterNextRender(albumsSubpage);
    const artAlbumDialog =
        albumsSubpage.shadowRoot!.querySelector('art-album-dialog');
    assertTrue(!!artAlbumDialog, '!!artAlbumDialog');
    await waitAfterNextRender(artAlbumDialog);
    assertTrue(artAlbumDialog.$.dialog.open, 'artAlbumDialog.$.dialog.open');
  });

  test('has correct album preview information', async () => {
    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(AmbientActionName.SET_ALBUMS);

    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action = await personalizationStore.waitForAction(
                       AmbientActionName.SET_ALBUMS) as SetAlbumsAction;
    assertEquals(6, action.albums.length);
    const ambientPreview = ambientSubpageElement.shadowRoot!.querySelector(
        'ambient-preview-small');
    assertTrue(!!ambientPreview);

    const previewImage =
        ambientPreview.shadowRoot!.querySelector<HTMLImageElement>(
            '.preview-image');
    assertTrue(!!previewImage);
    assertTrue(previewImage.src.includes('test_url2'));

    const previewAlbumTitle =
        ambientPreview.shadowRoot!.getElementById('albumTitle');
    assertTrue(!!previewAlbumTitle);
    assertEquals('2', previewAlbumTitle.innerText.replace(/\s/g, ''));
  });

  test('displays zero state when ambient mode is disabled', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ false);

    const mainSettings =
        ambientSubpageElement.shadowRoot!.querySelector('#mainSettings');
    assertTrue(!!mainSettings, 'main settings should be present');

    assertEquals(
        null, mainSettings.querySelector('ambient-preview-small'),
        'preview image should be absent');

    assertEquals(
        null,
        ambientSubpageElement.shadowRoot!.querySelector('ambient-theme-list'),
        'ambient theme list should be absent');

    assertEquals(
        null,
        ambientSubpageElement.shadowRoot!.querySelector('topic-source-list'),
        'topic source list should be absent');

    assertEquals(
        null,
        ambientSubpageElement.shadowRoot!.querySelector('ambient-weather-unit'),
        'weather unit should be absent');

    const zeroState =
        ambientSubpageElement.shadowRoot!.querySelector('toggle-row');
    assertTrue(!!zeroState, 'zero state should be present');
  });

  test('displays ambient preview when ambient mode is disabled', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ false);

    const ambientPreview = ambientSubpageElement.shadowRoot!.querySelector(
        'ambient-preview-small');
    assertTrue(!!ambientPreview, 'preview image should be present');

    assertEquals(
        null,
        ambientSubpageElement.shadowRoot!.querySelector('topic-source-list'),
        'topic source list should be absent');

    assertEquals(
        null,
        ambientSubpageElement.shadowRoot!.querySelector('ambient-weather-unit'),
        'weather unit should be absent');

    assertEquals(
        null,
        ambientSubpageElement.shadowRoot!.querySelector('ambient-zero-state'),
        'zero state should not be present');
  });

  test('preview and downloading buttons should be present', async () => {
    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const ambientPreview = ambientSubpageElement.shadowRoot!.querySelector(
        'ambient-preview-small');
    assertTrue(!!ambientPreview, 'ambient-preview element exists');

    const previewButton =
        ambientPreview.shadowRoot!.querySelector('.preview-button');
    assertTrue(!!previewButton, 'preview button should be present');

    personalizationStore.data.ambient.ambientUiVisibility =
        AmbientUiVisibility.kPreview;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);
    const downloadingButton =
        ambientPreview.shadowRoot!.querySelector('.preview-button-disabled');
    assertTrue(!!downloadingButton, 'downloading button should be present');
  });

  test('shows video ambient theme on supported devices', async () => {
    // Enabled `isTimeOfDayScreensaverEnabled` to show the updated UI.
    loadTimeData.overrideValues({'isTimeOfDayScreenSaverEnabled': true});

    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true);

    const ambientThemeList =
        ambientSubpageElement.shadowRoot!.querySelector('ambient-theme-list');
    assertTrue(!!ambientThemeList);

    const ambientThemeItems =
        ambientThemeList!.shadowRoot!.querySelectorAll<AmbientThemeItemElement>(
            'ambient-theme-item:not([hidden])');
    assertEquals(4, ambientThemeItems.length);
    const videoTheme = ambientThemeItems[3] as AmbientThemeItemElement;
    assertEquals(AmbientTheme.kVideo, videoTheme.ambientTheme);
    assertEquals('false', videoTheme.ariaChecked);

    personalizationStore.expectAction(AmbientActionName.SET_AMBIENT_THEME);
    videoTheme.click();
    const action =
        await personalizationStore.waitForAction(
            AmbientActionName.SET_AMBIENT_THEME) as SetAmbientThemeAction;
    assertEquals(AmbientTheme.kVideo, action.ambientTheme);
  });

  test('disables non-video topic sources for video animation', async () => {
    // Enabled `isTimeOfDayScreensaverEnabled` to show the updated UI.
    loadTimeData.overrideValues({'isTimeOfDayScreenSaverEnabled': true});

    ambientSubpageElement = await displayMainSettings(
        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
        /*ambientModeEnabled=*/ true, AmbientTheme.kVideo);

    const topicSourceList =
        ambientSubpageElement.shadowRoot!.querySelector('topic-source-list');
    assertTrue(!!topicSourceList);
    const topicSourceItems =
        topicSourceList.shadowRoot!.querySelectorAll('topic-source-item');
    assertEquals(3, topicSourceItems!.length);
    const video = topicSourceItems[0] as TopicSourceItemElement;
    const googlePhotos = topicSourceItems[1] as TopicSourceItemElement;
    const art = topicSourceItems[2] as TopicSourceItemElement;
    assertEquals(TopicSource.kGooglePhotos, googlePhotos.topicSource);
    assertEquals(TopicSource.kArtGallery, art.topicSource);

    assertFalse(video.disabled);
    assertTrue(googlePhotos.disabled);
    assertTrue(art.disabled);
  });

  test('cannot deselect a video album', async () => {
    personalizationStore.setReducersEnabled(true);
    personalizationStore.expectAction(AmbientActionName.SET_ALBUMS);
    ambientSubpageElement = initElement(AmbientSubpageElement, {
      path: Paths.AMBIENT_ALBUMS,
      queryParams: {topicSource: TopicSource.kVideo},
    });
    personalizationStore.data.ambient.ambientModeEnabled = true;
    personalizationStore.data.ambient.albums = ambientProvider.albums;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(ambientSubpageElement);

    await ambientProvider.whenCalled('setAmbientObserver');
    ambientProvider.updateAmbientObserver();

    const action = await personalizationStore.waitForAction(
                       AmbientActionName.SET_ALBUMS) as SetAlbumsAction;
    assertEquals(6, action.albums.length);

    const albumsSubpage =
        ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
    assertTrue(!!albumsSubpage);
    assertFalse(albumsSubpage.hidden);
    await waitAfterNextRender(albumsSubpage);

    const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
    assertTrue(!!albumList);

    const albums =
        albumList.shadowRoot!.querySelectorAll<WallpaperGridItemElement>(
            'wallpaper-grid-item:not([hidden])');
    assertEquals(2, albums.length);
    assertTrue(!!albums[0]);
    assertTrue(!!albums[1]);
    assertTrue(albums[0].selected!);
    assertFalse(albums[1].selected!);

    // Attempt to de-select the selected album and expect that the album is
    // still selected.
    albums[0].click();
    assertTrue(albums[0].selected!);
  });

  test(
      'dismisses the time of day banner if ambient mode is enabled',
      async () => {
        personalizationStore.setReducersEnabled(true);
        ambientSubpageElement = await displayMainSettings(
            TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
            /*ambientModeEnabled=*/ false);

        personalizationStore.data.ambient.shouldShowTimeOfDayBanner = true;
        personalizationStore.data.ambient.ambientModeEnabled = true;
        personalizationStore.notifyObservers();
        await waitAfterNextRender(ambientSubpageElement);
        assertFalse(
            personalizationStore.data.ambient.shouldShowTimeOfDayBanner,
            'banner is dismissed');
      });
});