chromium/chrome/test/data/webui/settings/about_page_test.ts

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

// clang-format off
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import type {SettingsAboutPageElement, SettingsRoutes} from 'chrome://settings/settings.js';
import {AboutPageBrowserProxyImpl, LifetimeBrowserProxyImpl, Route, Router, resetRouterForTesting} from 'chrome://settings/settings.js';
import {assertTrue} from 'chrome://webui-test/chai_assert.js';

import {TestAboutPageBrowserProxy} from './test_about_page_browser_proxy.js';
import {TestLifetimeBrowserProxy} from './test_lifetime_browser_proxy.js';

// <if expr="_google_chrome">
import {ABOUT_PAGE_PRIVACY_POLICY_URL, OpenWindowProxyImpl} from 'chrome://settings/settings.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {TestOpenWindowProxy} from 'chrome://webui-test/test_open_window_proxy.js';
// </if>

// <if expr="_google_chrome and is_macosx">
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import type {PromoteUpdaterStatus} from 'chrome://settings/settings.js';
// </if>

// <if expr="not chromeos_ash">
import {UpdateStatus} from 'chrome://settings/settings.js';
import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {assertFalse, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
// </if>

// <if expr="_google_chrome or not chromeos_ash">
import {assertEquals} from 'chrome://webui-test/chai_assert.js';
// </if>

// clang-format on

function setupRouter(): SettingsRoutes {
  const routes = {
    ABOUT: new Route('/help'),
    ADVANCED: new Route('/advanced'),
    BASIC: new Route('/'),
  } as unknown as SettingsRoutes;
  resetRouterForTesting(new Router(routes));
  return routes;
}

// <if expr="not chromeos_ash">
function fireStatusChanged(
    status: UpdateStatus, options: {progress?: number, message?: string} = {}) {
  webUIListenerCallback('update-status-changed', {
    progress: options.progress === undefined ? 1 : options.progress,
    message: options.message,
    status: status,
  });
}
// </if>

suite('AllBuilds', function() {
  let page: SettingsAboutPageElement;
  let aboutBrowserProxy: TestAboutPageBrowserProxy;
  let lifetimeBrowserProxy: TestLifetimeBrowserProxy;

  let testRoutes: SettingsRoutes;

  setup(function() {
    loadTimeData.overrideValues({
      aboutObsoleteNowOrSoon: false,
      aboutObsoleteEndOfTheLine: false,
    });

    testRoutes = setupRouter();
    lifetimeBrowserProxy = new TestLifetimeBrowserProxy();
    LifetimeBrowserProxyImpl.setInstance(lifetimeBrowserProxy);

    aboutBrowserProxy = new TestAboutPageBrowserProxy();
    AboutPageBrowserProxyImpl.setInstance(aboutBrowserProxy);
    return initNewPage();
  });

  teardown(function() {
    page.remove();
  });

  function initNewPage(): Promise<void> {
    aboutBrowserProxy.reset();
    lifetimeBrowserProxy.reset();
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    page = document.createElement('settings-about-page');
    Router.getInstance().navigateTo(testRoutes.ABOUT);
    document.body.appendChild(page);
    // <if expr="chromeos_ash">
    return Promise.resolve();
    // </if>

    // <if expr="not chromeos_ash">
    return aboutBrowserProxy.whenCalled('refreshUpdateStatus');
    // </if>
  }

  // <if expr="not chromeos_ash">
  const SPINNER_ICON: string = 'chrome://resources/images/throbber_small.svg';

  /**
   * Test that the status icon and status message update according to
   * incoming 'update-status-changed' events.
   */
  test('IconAndMessageUpdates', function() {
    const icon = page.shadowRoot!.querySelector('iron-icon')!;
    assertTrue(!!icon);
    const statusMessageEl =
        page.shadowRoot!.querySelector('#updateStatusMessage div')!;
    let previousMessageText = statusMessageEl.textContent;

    fireStatusChanged(UpdateStatus.CHECKING);
    assertEquals(SPINNER_ICON, icon.src);
    assertEquals(null, icon.getAttribute('icon'));
    assertNotEquals(previousMessageText, statusMessageEl.textContent);
    previousMessageText = statusMessageEl.textContent;

    fireStatusChanged(UpdateStatus.UPDATING, {progress: 0});
    assertEquals(SPINNER_ICON, icon.src);
    assertEquals(null, icon.getAttribute('icon'));
    assertFalse(statusMessageEl.textContent!.includes('%'));
    assertNotEquals(previousMessageText, statusMessageEl.textContent);
    previousMessageText = statusMessageEl.textContent;

    fireStatusChanged(UpdateStatus.UPDATING, {progress: 1});
    assertNotEquals(previousMessageText, statusMessageEl.textContent);
    assertTrue(statusMessageEl.textContent!.includes('%'));
    previousMessageText = statusMessageEl.textContent;

    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
    assertEquals(null, icon.src);
    assertEquals('settings:check-circle', icon.icon);
    assertNotEquals(previousMessageText, statusMessageEl.textContent);
    previousMessageText = statusMessageEl.textContent;

    fireStatusChanged(UpdateStatus.DISABLED_BY_ADMIN);
    assertEquals(null, icon.src);
    assertEquals('cr20:domain', icon.icon);
    assertEquals(0, statusMessageEl.textContent!.trim().length);

    fireStatusChanged(UpdateStatus.FAILED);
    assertEquals(null, icon.src);
    assertEquals('cr:error', icon.icon);
    assertEquals(0, statusMessageEl.textContent!.trim().length);

    fireStatusChanged(UpdateStatus.DISABLED);
    assertEquals(null, icon.src);
    assertEquals(null, icon.getAttribute('icon'));
    assertEquals(0, statusMessageEl.textContent!.trim().length);
  });

  test('ErrorMessageWithHtml', function() {
    const htmlError = 'hello<br>there<br>was<pre>an</pre>error';
    fireStatusChanged(UpdateStatus.FAILED, {message: htmlError});
    const statusMessageEl =
        page.shadowRoot!.querySelector('#updateStatusMessage div');
    assertEquals(htmlError, statusMessageEl!.innerHTML);
  });

  test('FailedLearnMoreLink', function() {
    // Check that link is shown when update failed.
    fireStatusChanged(UpdateStatus.FAILED, {message: 'foo'});
    assertTrue(!!page.shadowRoot!.querySelector(
        '#updateStatusMessage a:not([hidden])'));

    // Check that link is hidden when update hasn't failed.
    fireStatusChanged(UpdateStatus.UPDATED, {message: ''});
    assertTrue(
        !!page.shadowRoot!.querySelector('#updateStatusMessage a[hidden]'));
  });

  /**
   * Test that when the current platform has been marked as deprecated
   * (but not end of the line) a deprecation warning message is displayed,
   * without interfering with the update status message and icon.
   */
  test('ObsoleteSystem', async () => {
    loadTimeData.overrideValues({
      aboutObsoleteNowOrSoon: true,
      aboutObsoleteEndOfTheLine: false,
    });

    function queryDeprecationWarning() {
      return page.shadowRoot!.querySelector<HTMLElement>('#deprecationWarning')!
          ;
    }

    function queryUpdateStatusMessage() {
      return page.shadowRoot!.querySelector<HTMLElement>(
          '#updateStatusMessage')!;
    }

    await initNewPage();
    const icon = page.shadowRoot!.querySelector('iron-icon')!;
    assertTrue(!!icon);
    assertTrue(!!queryUpdateStatusMessage());
    assertTrue(!!queryDeprecationWarning());
    assertFalse(queryDeprecationWarning().hidden);

    fireStatusChanged(UpdateStatus.CHECKING);
    assertEquals(SPINNER_ICON, icon.src);
    assertEquals(null, icon.getAttribute('icon'));
    assertFalse(queryDeprecationWarning().hidden);
    assertFalse(queryUpdateStatusMessage().hidden);

    fireStatusChanged(UpdateStatus.UPDATING);
    assertEquals(SPINNER_ICON, icon.src);
    assertEquals(null, icon.getAttribute('icon'));
    assertFalse(queryDeprecationWarning().hidden);
    assertFalse(queryUpdateStatusMessage().hidden);

    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
    assertEquals(null, icon.src);
    assertEquals('settings:check-circle', icon.icon);
    assertFalse(queryDeprecationWarning().hidden);
    assertFalse(queryUpdateStatusMessage().hidden);
  });

  /**
   * Test that when the current platform has reached the end of the line,
   * a deprecation warning message and an error icon is displayed.
   */
  test('ObsoleteSystemEndOfLine', async () => {
    loadTimeData.overrideValues({
      aboutObsoleteNowOrSoon: true,
      aboutObsoleteEndOfTheLine: true,
    });

    function queryDeprecationWarning() {
      return page.shadowRoot!.querySelector<HTMLElement>('#deprecationWarning')!
          ;
    }

    function queryUpdateStatusMessage() {
      return page.shadowRoot!.querySelector<HTMLElement>(
          '#updateStatusMessage')!;
    }

    await initNewPage();
    const icon = page.shadowRoot!.querySelector('iron-icon')!;
    assertTrue(!!icon);
    assertTrue(!!queryDeprecationWarning());
    assertTrue(!!queryUpdateStatusMessage());

    assertFalse(queryDeprecationWarning().hidden);
    assertTrue(queryUpdateStatusMessage().hidden);

    fireStatusChanged(UpdateStatus.CHECKING);
    assertEquals(null, icon.src);
    assertEquals('cr:error', icon.icon);
    assertFalse(queryDeprecationWarning().hidden);
    assertTrue(queryUpdateStatusMessage().hidden);

    fireStatusChanged(UpdateStatus.FAILED);
    assertEquals(null, icon.src);
    assertEquals('cr:error', icon.icon);
    assertFalse(queryDeprecationWarning().hidden);
    assertTrue(queryUpdateStatusMessage().hidden);

    fireStatusChanged(UpdateStatus.UPDATED);
    assertEquals(null, icon.src);
    assertEquals('cr:error', icon.icon);
    assertFalse(queryDeprecationWarning().hidden);
    assertTrue(queryUpdateStatusMessage().hidden);
  });

  test('Relaunch', function() {
    let relaunch = page.shadowRoot!.querySelector<HTMLElement>('#relaunch')!;
    assertTrue(!!relaunch);
    assertTrue(relaunch.hidden);

    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
    assertFalse(relaunch.hidden);

    relaunch = page.shadowRoot!.querySelector<HTMLElement>('#relaunch')!;
    assertTrue(!!relaunch);
    relaunch.click();
    return lifetimeBrowserProxy.whenCalled('relaunch');
  });

  /*
   * Test that the "Relaunch" button updates according to incoming
   * 'update-status-changed' events.
   */
  test('ButtonsUpdate', function() {
    const relaunch = page.shadowRoot!.querySelector<HTMLElement>('#relaunch')!;
    assertTrue(!!relaunch);

    fireStatusChanged(UpdateStatus.CHECKING);
    assertTrue(relaunch.hidden);

    fireStatusChanged(UpdateStatus.UPDATING);
    assertTrue(relaunch.hidden);

    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
    assertFalse(relaunch.hidden);

    fireStatusChanged(UpdateStatus.UPDATED);
    assertTrue(relaunch.hidden);

    fireStatusChanged(UpdateStatus.FAILED);
    assertTrue(relaunch.hidden);

    fireStatusChanged(UpdateStatus.DISABLED);
    assertTrue(relaunch.hidden);

    fireStatusChanged(UpdateStatus.DISABLED_BY_ADMIN);
    assertTrue(relaunch.hidden);
  });

  // <if expr="_google_chrome or _is_chrome_for_testing_branded">
  test('TermsOfService', function() {
    const termsOfServiceEl =
        page.shadowRoot!.querySelector<HTMLAnchorElement>('a#tos');
    assertTrue(!!termsOfServiceEl);

    assertEquals(page.i18n('aboutProductTos'), termsOfServiceEl.textContent);
    assertEquals(page.i18n('aboutTermsURL'), termsOfServiceEl.href);
  });
  // </if>

  // </if>
  test('GetHelp', function() {
    assertTrue(!!page.shadowRoot!.querySelector('#help'));
    page.shadowRoot!.querySelector<HTMLElement>('#help')!.click();
    return aboutBrowserProxy.whenCalled('openHelpPage');
  });
});

// <if expr="_google_chrome">
suite('OfficialBuild', function() {
  let page: SettingsAboutPageElement;
  let browserProxy: TestAboutPageBrowserProxy;
  let openWindowProxy: TestOpenWindowProxy;
  let testRoutes: SettingsRoutes;

  setup(function() {
    testRoutes = setupRouter();
    browserProxy = new TestAboutPageBrowserProxy();
    AboutPageBrowserProxyImpl.setInstance(browserProxy);
    openWindowProxy = new TestOpenWindowProxy();
    OpenWindowProxyImpl.setInstance(openWindowProxy);
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    page = document.createElement('settings-about-page');
    Router.getInstance().navigateTo(testRoutes.ABOUT);
    document.body.appendChild(page);
    return flushTasks();
  });

  test('ReportAnIssue', async function() {
    assertTrue(!!page.shadowRoot!.querySelector('#reportIssue'));
    page.shadowRoot!.querySelector<HTMLElement>('#reportIssue')!.click();
    await browserProxy.whenCalled('openFeedbackDialog');
  });

  test('PrivacyPolicy', async function() {
    const privacyPolicyLink =
        page.shadowRoot!.querySelector<HTMLElement>('#privacyPolicy');
    assertTrue(!!privacyPolicyLink);
    privacyPolicyLink.click();
    const url = await openWindowProxy.whenCalled('openUrl');
    assertEquals(ABOUT_PAGE_PRIVACY_POLICY_URL, url);
  });

  // <if expr="is_macosx">
  type Scenarios = 'CANT_PROMOTE'|'CAN_PROMOTE'|'IN_BETWEEN'|'PROMOTED';

  /**
   * A list of possible scenarios for the promoteUpdater.
   */
  const PromoStatusScenarios: {[key in Scenarios]: PromoteUpdaterStatus} = {
    CANT_PROMOTE: {
      hidden: true,
      disabled: true,
      actionable: false,
    },
    CAN_PROMOTE: {
      hidden: false,
      disabled: false,
      actionable: true,
    },
    IN_BETWEEN: {
      hidden: false,
      disabled: true,
      actionable: true,
    },
    PROMOTED: {
      hidden: false,
      disabled: true,
      actionable: false,
    },
  };

  function firePromoteUpdaterStatusChanged(status: PromoteUpdaterStatus) {
    webUIListenerCallback('promotion-state-changed', status);
  }

  /**
   * Tests that the button's states are wired up to the status correctly.
   */
  test('PromoteUpdaterButtonCorrectStates', function() {
    function queryPromoteUpdater() {
      return page.shadowRoot!.querySelector<HTMLElement>('#promoteUpdater');
    }

    function queryArrowIcon() {
      return page.shadowRoot!.querySelector<HTMLElement>(
          '#promoteUpdater cr-icon-button');
    }

    let item = queryPromoteUpdater();
    let arrow = queryArrowIcon();
    assertFalse(!!item);
    assertFalse(!!arrow);

    firePromoteUpdaterStatusChanged(PromoStatusScenarios.CANT_PROMOTE);
    flush();
    item = queryPromoteUpdater();
    arrow = queryArrowIcon();
    assertFalse(!!item);
    assertFalse(!!arrow);

    firePromoteUpdaterStatusChanged(PromoStatusScenarios.CAN_PROMOTE);
    flush();

    item = queryPromoteUpdater();
    assertTrue(!!item);
    assertFalse(item!.hasAttribute('disabled'));
    assertTrue(item!.hasAttribute('actionable'));

    arrow = queryArrowIcon();
    assertTrue(!!arrow);
    assertEquals('CR-ICON-BUTTON', arrow!.parentElement!.tagName);
    assertFalse(arrow!.parentElement!.hidden);
    assertFalse(arrow!.hasAttribute('disabled'));

    firePromoteUpdaterStatusChanged(PromoStatusScenarios.IN_BETWEEN);
    flush();
    item = queryPromoteUpdater();
    assertTrue(!!item);
    assertTrue(item!.hasAttribute('disabled'));
    assertTrue(item!.hasAttribute('actionable'));

    arrow = queryArrowIcon();
    assertTrue(!!arrow);
    assertEquals('CR-ICON-BUTTON', arrow!.parentElement!.tagName);
    assertFalse(arrow!.parentElement!.hidden);
    assertTrue(arrow!.hasAttribute('disabled'));

    firePromoteUpdaterStatusChanged(PromoStatusScenarios.PROMOTED);
    flush();
    item = queryPromoteUpdater();
    assertTrue(!!item);
    assertTrue(item!.hasAttribute('disabled'));
    assertFalse(item!.hasAttribute('actionable'));

    arrow = queryArrowIcon();
    assertTrue(!!arrow);
    assertEquals('CR-ICON-BUTTON', arrow!.parentElement!.tagName);
    assertTrue(arrow!.parentElement!.hidden);
    assertTrue(arrow!.hasAttribute('disabled'));
  });

  test('PromoteUpdaterButtonWorksWhenEnabled', async function() {
    firePromoteUpdaterStatusChanged(PromoStatusScenarios.CAN_PROMOTE);
    flush();
    const item = page.shadowRoot!.querySelector<HTMLElement>('#promoteUpdater');
    assertTrue(!!item);

    item!.click();

    await browserProxy.whenCalled('promoteUpdater');
  });
  // </if>
});
// </if>