chromium/chrome/test/data/webui/chromeos/personalization_app/sea_pen_images_element_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://personalization/strings.m.js';
import 'chrome://webui-test/chromeos/mojo_webui_test_support.js';

import {SeaPenErrorElement, SeaPenImageLoadingElement, SeaPenImagesElement, SeaPenRouterElement, SeaPenZeroStateSvgElement, setSeaPenThumbnailsAction, setSelectedRecentSeaPenImageAction, setTransitionsEnabled, SparklePlaceholderElement, WallpaperGridItemElement} from 'chrome://personalization/js/personalization_app.js';
import {CrIconButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
import {PromiseResolver} from 'chrome://resources/ash/common/promise_resolver.js';
import {MantaStatusCode, SeaPenThumbnail} from 'chrome://resources/ash/common/sea_pen/sea_pen.mojom-webui.js';
import {SeaPenTemplateId} from 'chrome://resources/ash/common/sea_pen/sea_pen_generated.mojom-webui.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PaperSpinnerLiteElement} from 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {isVisible} from 'chrome://webui-test/test_util.js';

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

suite('SeaPenImagesElementTest', function() {
  let personalizationStore: TestPersonalizationStore;
  let seaPenProvider: TestSeaPenProvider;
  let seaPenImagesElement: SeaPenImagesElement|null;

  function getWallpaperGridItems(): WallpaperGridItemElement[] {
    return Array.from(seaPenImagesElement!.shadowRoot!
                          .querySelectorAll<WallpaperGridItemElement>(
                              `div:not([hidden]).thumbnail-item-container ` +
                              `wallpaper-grid-item:not([hidden])`));
  }

  function getThumbnailLoadingElements(): SeaPenImageLoadingElement[] {
    return Array.from(seaPenImagesElement!.shadowRoot!.querySelectorAll<
                      SeaPenImageLoadingElement>(
        `div:not([hidden]).thumbnail-item-container sea-pen-image-loading`));
  }

  setup(() => {
    loadTimeData.overrideValues({isSeaPenEnabled: true});
    const mocks = baseSetup();
    personalizationStore = mocks.personalizationStore;
    seaPenProvider = mocks.seaPenProvider;
    // Disables animation by default.
    setTransitionsEnabled(false);
  });

  teardown(async () => {
    await teardownElement(seaPenImagesElement);
    seaPenImagesElement = null;
  });

  test('displays zero state SVG', async () => {
    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const loadingThumbnailPlaceholder =
        seaPenImagesElement.shadowRoot!.querySelector('.placeholder');
    assertFalse(
        !!loadingThumbnailPlaceholder,
        'thumbnails should not be in loading state');

    assertTrue(
        !!seaPenImagesElement.shadowRoot!.querySelector(
            SeaPenZeroStateSvgElement.is),
        'sea-pen-zero-state-svg is shown initially');
    assertTrue(
        !!seaPenImagesElement.shadowRoot!.querySelector('.zero-state-message'),
        'zero state message is shown');
  });

  test('displays 8 loading thumbnail placeholders for template', async () => {
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = true;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const loadingThumbnailPlaceholders =
        seaPenImagesElement.shadowRoot!
            .querySelectorAll<SparklePlaceholderElement>(
                'div:not([hidden]) .loading-placeholder > sparkle-placeholder');
    assertEquals(
        8, loadingThumbnailPlaceholders!.length,
        'should be 8 loading placeholders available.');
    assertTrue(Array.from(loadingThumbnailPlaceholders)
                   .every(placeholder => !!placeholder.active));
  });

  test('displays 4 loading thumbnail placeholders for freeform', async () => {
    loadTimeData.overrideValues({isSeaPenTextInputEnabled: true});
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = true;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    seaPenImagesElement =
        initElement(SeaPenImagesElement, {templateId: 'Query'});
    await waitAfterNextRender(seaPenImagesElement);

    const loadingThumbnailPlaceholders =
        seaPenImagesElement.shadowRoot!
            .querySelectorAll<SparklePlaceholderElement>(
                'div:not([hidden]) .loading-placeholder > sparkle-placeholder');
    assertEquals(
        4, loadingThumbnailPlaceholders!.length,
        'should be 4 loading placeholders available.');
    assertTrue(Array.from(loadingThumbnailPlaceholders)
                   .every(placeholder => !!placeholder.active));
  });

  test('thumbnail placeholders not active when hidden', async () => {
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const loadingThumbnailPlaceholders =
        seaPenImagesElement.shadowRoot!
            .querySelectorAll<SparklePlaceholderElement>(
                'div:not([hidden]) sparkle-placeholder');
    assertTrue(Array.from(loadingThumbnailPlaceholders)
                   .every(placeholder => !placeholder.active));
  });

  test('displays image thumbnails', async () => {
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const thumbnails = seaPenImagesElement.shadowRoot!.querySelectorAll(
        'div:not([hidden]).thumbnail-item-container');
    assertEquals(4, thumbnails!.length, 'should be 4 images available.');
  });

  test('displays freeform history', async () => {
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;
    personalizationStore.data.wallpaper.seaPen.currentSeaPenQuery = {
      textQuery: 'test freeform query',
    };
    personalizationStore.data.wallpaper.seaPen.textQueryHistory = [
      {
        query: 'test freeform query',
        thumbnails: personalizationStore.data.wallpaper.seaPen.thumbnails =
            seaPenProvider.thumbnails,
      },
      {
        query: 'test freeform query 1',
        thumbnails: personalizationStore.data.wallpaper.seaPen.thumbnails =
            seaPenProvider.thumbnails,
      },
    ];

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const lastQuery =
        seaPenImagesElement.shadowRoot!.getElementById('queryHistoryHeading0');
    assertTrue(!!lastQuery, 'last query is available');
    assertEquals(
        'test freeform query', lastQuery.textContent?.trim(),
        'unexpected query');
    const thumbnails = seaPenImagesElement.shadowRoot!.querySelectorAll(
        'div:not([hidden]).thumbnail-item-container.history-item');
    assertEquals(8, thumbnails!.length, 'should be 8 images available.');
  });

  test('manages loading and selected when thumbnail clicked', async () => {
    personalizationStore.setReducersEnabled(true);
    seaPenImagesElement = initElement(SeaPenImagesElement, {templateId: 10});
    await waitAfterNextRender(seaPenImagesElement);

    // Simulate a query was run and we got the thumbnails, one of the sea pen
    // thumbnails was selected.
    personalizationStore.dispatch(setSeaPenThumbnailsAction(
        seaPenProvider.seaPenQuery, seaPenProvider.thumbnails));
    personalizationStore.dispatch(
        setSelectedRecentSeaPenImageAction(seaPenProvider.thumbnails[1]!.id));
    await waitAfterNextRender(seaPenImagesElement);

    let thumbnails = getWallpaperGridItems();
    assertEquals(4, thumbnails!.length, 'should be 4 images available');
    let imageThumbnailGrid =
        seaPenImagesElement!.shadowRoot!.querySelector('#grid');
    assertTrue(
        isVisible(imageThumbnailGrid!), 'thumbnail grid should be visible');
    assertDeepEquals(
        [false, true, false, false],
        thumbnails.map(thumbnail => thumbnail.selected),
        'index 1 thumbnail shows as selected');

    let thumbnailSelectedLoadingElement: SeaPenImageLoadingElement[] =
        getThumbnailLoadingElements();
    assertEquals(
        0, thumbnailSelectedLoadingElement!.length,
        'should be 0 loading elements');

    // Simulate the request starting with a user click on a thumbnail.
    const selectSeaPenThumbnailResolver =
        new PromiseResolver<{success: boolean}>();
    seaPenProvider.selectSeaPenThumbnailResponse =
        selectSeaPenThumbnailResolver.promise;
    thumbnails[0]!.click();
    await waitAfterNextRender(seaPenImagesElement);

    thumbnails = getWallpaperGridItems();
    assertEquals(4, thumbnails!.length, 'still 4 images available after click');
    imageThumbnailGrid =
        seaPenImagesElement!.shadowRoot!.querySelector('#grid');
    assertTrue(
        isVisible(imageThumbnailGrid!), 'thumbnail grid should be visible');
    assertDeepEquals(
        [true, false, false, false],
        thumbnails.map(thumbnail => thumbnail.selected),
        'index 0 thumbnail shows as selected after click');

    thumbnailSelectedLoadingElement = getThumbnailLoadingElements();
    assertEquals(
        1, thumbnailSelectedLoadingElement!.length,
        'should be 1 loading element');
    const spinner: PaperSpinnerLiteElement|null =
        thumbnailSelectedLoadingElement[0]!.shadowRoot!.querySelector(
            'paper-spinner-lite:not([hidden])');
    assertTrue(!!spinner, 'there should be a spinner in the loading element');
    const loadingText =
        thumbnailSelectedLoadingElement[0]!.shadowRoot!.querySelector(
            'p:not([hidden])');
    assertEquals(
        seaPenImagesElement.i18n('seaPenCreatingHighResImage'),
        loadingText!.textContent, 'the loading text should be correct');
    assertEquals(
        (personalizationStore.data.wallpaper.seaPen.pendingSelected as
         SeaPenThumbnail)
            .image,
        thumbnailSelectedLoadingElement[0]!.parentElement
            ?.querySelector('wallpaper-grid-item')
            ?.src,
        'sibling wallpaper-grid-item has expected src');
    assertEquals(
        true,
        thumbnailSelectedLoadingElement[0]!.parentElement
            ?.querySelector('wallpaper-grid-item')
            ?.selected,
        'sibling wallpaper-grid-item is selected');

    // Simulate the request resolving.
    selectSeaPenThumbnailResolver.resolve({success: true});
    await waitAfterNextRender(seaPenImagesElement);
    // Simulate receiving a confirmation that the sea pen image was selected.
    personalizationStore.dispatch(
        setSelectedRecentSeaPenImageAction(seaPenProvider.thumbnails[0]!.id));
    await waitAfterNextRender(seaPenImagesElement);

    thumbnails = getWallpaperGridItems();
    assertEquals(
        4, thumbnails!.length, 'still 4 images available after resolve');
    imageThumbnailGrid =
        seaPenImagesElement!.shadowRoot!.querySelector('#grid');
    assertTrue(
        isVisible(imageThumbnailGrid!), 'thumbnail grid should be visible');
    assertDeepEquals(
        [true, false, false, false],
        thumbnails.map(thumbnail => thumbnail.selected),
        'index 0 thumbnail still selected after resolve');
    thumbnailSelectedLoadingElement = getThumbnailLoadingElements();
    assertEquals(
        0, thumbnailSelectedLoadingElement!.length, 'no more loading element');
  });

  test('display feedback buttons', async () => {
    loadTimeData.overrideValues({isManagedSeaPenFeedbackEnabled: true});
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const feedbackButtons: CrIconButtonElement[] = Array.from(
        seaPenImagesElement.shadowRoot!.querySelectorAll<CrIconButtonElement>(
            `div:not([hidden]).thumbnail-item-container sea-pen-feedback`));
    assertTrue(feedbackButtons.length > 0);
  });

  test('hide feedback buttons if managed feedback disabled', async () => {
    loadTimeData.overrideValues({isManagedSeaPenFeedbackEnabled: false});
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    assertFalse(
        !!seaPenImagesElement.shadowRoot!.querySelector<CrIconButtonElement>(
            `div:not([hidden]).thumbnail-item-container sea-pen-feedback`));
  });

  test('hide error state on success', async () => {
    personalizationStore.data.wallpaper.seaPen.thumbnailResponseStatusCode =
        MantaStatusCode.kOk;

    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const errorState =
        seaPenImagesElement.shadowRoot!.querySelector(SeaPenErrorElement.is);
    assertFalse(!!errorState, 'error state should be hidden on success');
  });

  test('hide error state while loading', async () => {
    personalizationStore.data.wallpaper.seaPen.thumbnailResponseStatusCode =
        MantaStatusCode.kGenericError;
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = true;

    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    const errorState =
        seaPenImagesElement.shadowRoot!.querySelector(SeaPenErrorElement.is);
    assertFalse(!!errorState, 'error state should be hidden while loading');
  });

  test('switching templates while loading resets loading state', async () => {
    personalizationStore.setReducersEnabled(true);
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = true;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement);
    initElement(SeaPenRouterElement, {basePath: '/base'});
    await waitAfterNextRender(seaPenImagesElement);

    const loadingThumbnailPlaceholders =
        seaPenImagesElement.shadowRoot!
            .querySelectorAll<SparklePlaceholderElement>(
                'div:not([hidden]) sparkle-placeholder');
    assertEquals(
        8, loadingThumbnailPlaceholders!.length,
        'should be 8 loading placeholders available.');
    assertTrue(Array.from(loadingThumbnailPlaceholders)
                   .every(placeholder => !!placeholder.active));

    SeaPenRouterElement.instance().selectSeaPenTemplate(
        SeaPenTemplateId.kGlowscapes);
    await waitAfterNextRender(seaPenImagesElement);

    assertFalse(
        personalizationStore.data.wallpaper.seaPen.loading.thumbnails,
        'thumbnails should no longer be loading');
    const loadingThumbnailPlaceholder =
        seaPenImagesElement.shadowRoot!.querySelector('.placeholder');
    assertFalse(
        !!loadingThumbnailPlaceholder,
        'thumbnails should not be in loading state');

    assertTrue(
        !!seaPenImagesElement.shadowRoot!.querySelector(
            SeaPenZeroStateSvgElement.is),
        'sea-pen-zero-state-svg is shown');
    assertTrue(
        !!seaPenImagesElement.shadowRoot!.querySelector('.zero-state-message'),
        'zero state message is shown');
  });

  test('show images heading', async () => {
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement);
    await waitAfterNextRender(seaPenImagesElement);

    assertTrue(
        !!seaPenImagesElement.shadowRoot!.querySelector('#seaPenImagesHeading'),
        'seaPenImagesHeading is shown');
  });

  test('show images heading for template query', async () => {
    loadTimeData.overrideValues({isSeaPenTextInputEnabled: true});
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement = initElement(SeaPenImagesElement, {templateId: 3});
    await waitAfterNextRender(seaPenImagesElement);

    assertTrue(
        !!seaPenImagesElement.shadowRoot!.querySelector('#seaPenImagesHeading'),
        'seaPenImagesHeading is shown');
  });

  test('hide images heading for freeform query', async () => {
    loadTimeData.overrideValues({isSeaPenTextInputEnabled: true});
    personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
    personalizationStore.data.wallpaper.seaPen.thumbnails =
        seaPenProvider.thumbnails;

    // Initialize |seaPenImagesElement|.
    seaPenImagesElement =
        initElement(SeaPenImagesElement, {templateId: 'Query'});
    await waitAfterNextRender(seaPenImagesElement);

    assertFalse(
        !!seaPenImagesElement.shadowRoot!.querySelector('#seaPenImagesHeading'),
        'seaPenImagesHeading is hidden');
  });
});