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

// Copyright 2022 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 {DefaultUserImage, Paths, UserImage, UserPreviewElement} from 'chrome://personalization/js/personalization_app.js';
import {stringToMojoString16} from 'chrome://resources/js/mojo_type_util.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';

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

suite('UserPreviewElementTest', function() {
  let userPreviewElement: UserPreviewElement|null;
  let personalizationStore: TestPersonalizationStore;
  let userProvider: TestUserProvider;

  setup(() => {
    const mocks = baseSetup();
    personalizationStore = mocks.personalizationStore;
    userProvider = mocks.userProvider;
  });

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

  test('fetches user info on creation', async () => {
    assertEquals(0, userProvider.getCallCount('getUserInfo'));
    userPreviewElement = initElement(UserPreviewElement);
    await userProvider.whenCalled('getUserInfo');
  });

  test('displays user info when set', async () => {
    personalizationStore.data.user.info = userProvider.info;
    userPreviewElement = initElement(UserPreviewElement);
    await waitAfterNextRender(userPreviewElement!);

    assertEquals(
        userProvider.info.email,
        userPreviewElement!.shadowRoot!.getElementById('email')!.innerText);

    assertEquals(
        userProvider.info.name,
        userPreviewElement!.shadowRoot!.getElementById('name')!.innerText);
  });

  test('displays edit icon when not managed', async () => {
    personalizationStore.data.user.image = userProvider.image;
    personalizationStore.data.user.imageIsEnterpriseManaged = false;
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement);

    const avatarImage =
        userPreviewElement!.shadowRoot!.getElementById('avatar');
    assertFalse(avatarImage!.classList.contains('managed'));
    // Does show edit icon.
    assertTrue(
        !!userPreviewElement!.shadowRoot!.getElementById('iconContainer'));
    // Does not show enterprise icon.
    assertFalse(!!userPreviewElement!.shadowRoot!.getElementById(
        'enterpriseIconContainer'));
  });

  test('displays user image from default image', async () => {
    personalizationStore.data.user.image = userProvider.image;
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement!);

    const avatarImage = userPreviewElement!.shadowRoot!.getElementById(
                            'avatar') as HTMLImageElement;
    assertEquals(
        userProvider.image.defaultImage?.url!.url, avatarImage.src,
        'correct image url is shown for default image');
  });

  test('displays user image from profile image', async () => {
    personalizationStore.data.user.image = {profileImage: {}} as UserImage;
    personalizationStore.data.user.profileImage = userProvider.profileImage;
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement!);

    const avatarImage = userPreviewElement!.shadowRoot!.getElementById(
                            'avatar') as HTMLImageElement;
    assertEquals(
        userProvider.profileImage.url, avatarImage.src,
        'correct image url is shown for profile image');
    assertTrue(
        avatarImage.src.startsWith('data:'), 'data url is not sanitized');
    assertTrue(
        !avatarImage.style.backgroundImage,
        'data url does not have a background image');
  });

  test('displays user image from external image', async () => {
    // Use a cast here because generated types for |UserImage| are incorrect:
    // only one field should be specified at a time.
    const externalImage = {
      externalImage: {
        bytes: [0, 0, 0, 0],
        sharedMemory: undefined,
        invalidBuffer: false,
      },
    } as UserImage;
    personalizationStore.data.user.image = externalImage;

    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement);

    const avatarImage = userPreviewElement!.shadowRoot!.getElementById(
                            'avatar') as HTMLImageElement;

    assertTrue(
        avatarImage.src.startsWith('blob:'),
        'blob url is shown for external image');
    assertTrue(
        !avatarImage.style.backgroundImage,
        'blob url does not have a background image');
  });

  test('sanitizes gstatic image', async () => {
    personalizationStore.data.user.image = {
      'defaultImage': {
        url: {
          url: 'https://www.gstatic.com/',
        },
        title: stringToMojoString16('the remains of the day'),
        index: 1,
        sourceInfo: null,
      },
    };

    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement);

    const avatarImage = userPreviewElement!.shadowRoot!.getElementById(
                            'avatar') as HTMLImageElement;

    assertTrue(
        avatarImage.src.startsWith('chrome://image'), 'url was sanitized');
    assertTrue(
        avatarImage.style.backgroundImage.startsWith('url("chrome://image'),
        'background-url was sanitized');
  });

  test('do not display image if user image is not ready yet', async () => {
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement!);

    const avatarImage = userPreviewElement!.shadowRoot!.getElementById(
                            'avatar') as HTMLImageElement;
    assertEquals(
        null, avatarImage,
        'do not display image if user image is not ready yet');
  });

  test('displays placeholder image if user image is invalid', async () => {
    personalizationStore.data.user.image = {invalidImage: {}} as UserImage;
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement!);

    const avatarImage = userPreviewElement!.shadowRoot!.getElementById(
                            'avatar') as HTMLImageElement;
    assertEquals(
        'chrome://theme/IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE', avatarImage.src,
        'placeholder image is shown for invalid user image');
  });

  test('displays non-clickable user image on user subpage', async () => {
    personalizationStore.data.user.image = userProvider.image;
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.USER});
    await waitAfterNextRender(userPreviewElement);

    const avatarImage = userPreviewElement!.shadowRoot!.getElementById(
                            'avatar2') as HTMLImageElement;
    assertEquals(
        userProvider.image.defaultImage?.url!.url, avatarImage.src,
        'default image url is shown on non-clickable image');
  });

  test('displays enterprise logo on avatar image', async () => {
    personalizationStore.data.user.image = userProvider.image;
    personalizationStore.data.user.imageIsEnterpriseManaged = true;
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement);

    const avatarImage =
        userPreviewElement!.shadowRoot!.getElementById('avatar');
    assertTrue(avatarImage!.classList.contains('managed'));
    // Does not show edit icon.
    assertFalse(
        !!userPreviewElement!.shadowRoot!.getElementById('iconContainer'));
    // Does show enterprise icon.
    assertTrue(!!userPreviewElement!.shadowRoot!.getElementById(
        'enterpriseIconContainer'));
  });

  test('displays author and website source info if present', async () => {
    personalizationStore.data.user.image = userProvider.image;
    userPreviewElement = initElement(UserPreviewElement, {path: Paths.ROOT});
    await waitAfterNextRender(userPreviewElement);

    // Image has no sourceInfo so should be missing.
    assertFalse(!!userPreviewElement.shadowRoot!.getElementById('sourceInfo'));

    const deprecatedDefaultImage: DefaultUserImage = {
      index: 2,
      title: stringToMojoString16('title'),
      url: {url: 'data://test_url'},
      sourceInfo: {
        author: stringToMojoString16('author example'),
        website: {url: 'website example'},
      },
    };
    personalizationStore.data.user.image = {
      defaultImage: deprecatedDefaultImage,
    } as UserImage;
    personalizationStore.notifyObservers();
    await waitAfterNextRender(userPreviewElement);

    // |sourceInfo| element should now exist.
    const a = userPreviewElement.shadowRoot!.getElementById('sourceInfo');
    assertEquals('website example', a!.getAttribute('href'));
    assertEquals('author example', a!.querySelector('span')!.innerText);
  });
});