chromium/chrome/test/data/webui/settings/safety_hub_module_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.

// clang-format off
import 'chrome://settings/lazy_load.js';

import type {SettingsSafetyHubModuleElement} from 'chrome://settings/lazy_load.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {eventToPromise} from 'chrome://webui-test/test_util.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {isVisible} from 'chrome://webui-test/test_util.js';
// clang-format on

function waitUntilVisible(element: HTMLElement, intervalMs: number = 10) {
  return new Promise<void>((resolve) => {
    const interval = setInterval(() => {
      if (isVisible(element)) {
        clearInterval(interval);
        resolve();
      }
    }, intervalMs);
  });
}

suite('SafetyHubModule', function() {
  let testElement: SettingsSafetyHubModuleElement;

  const mockData = [1, 2, 3, 4].map(i => ({
                                      origin: `https://www.example${i}.com:443`,
                                      detail: `Detail ${i}`,
                                    }));

  function getEntries() {
    return testElement.shadowRoot!.querySelectorAll('.site-entry');
  }

  function assignAndShowTestData() {
    testElement.sites = mockData;
    testElement.setModelUpdateDelayMsForTesting(0);

    let callback: Function;
    const promise = new Promise((resolve) => {
      callback = resolve;
    });

    testElement.animateShow(mockData.map(data => data.origin), function() {
      // The animation delay is set to zero, so the animation will take place
      // instantaneously; but also asynchronously. Postpone the callback
      // to the end of the thread.
      setTimeout(callback, 0);
    });

    return promise;
  }

  setup(function() {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    testElement = document.createElement('settings-safety-hub-module');
    document.body.appendChild(testElement);
  });

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

  test('testHeaderAndSubheaderText', function() {
    const headerText = 'Test header text';
    const subheaderText = 'Test subheader text';
    testElement.header = headerText;
    testElement.subheader = subheaderText;
    flush();

    function assertTextContent(query: string, text: string) {
      const element = testElement.shadowRoot!.querySelector(query);
      assertTrue(!!element);
      assertEquals(text, element.textContent!.trim());
    }

    assertTextContent('#header', headerText);
    assertTextContent('#subheader', subheaderText);
  });

  test('testItemButton', async function() {
    await assignAndShowTestData();
    testElement.buttonIcon = 'cr20:block';
    testElement.buttonAriaLabelId =
        'safetyCheckNotificationPermissionReviewDontAllowAriaLabel';
    flush();

    // User clicks the button of the 2nd item in the list.
    const item = getEntries()[1]!;
    const button = item.querySelector('cr-icon-button');
    assertTrue(!!button);
    assertEquals('cr20:block', button!.ironIcon);

    const clickEventPromise =
        eventToPromise('sh-module-item-button-click', testElement);
    button.click();
    const e = await clickEventPromise;
    const clickedItem = e.detail;
    assertEquals(clickedItem.origin, mockData[1]!.origin);
    assertEquals(clickedItem.detail, mockData[1]!.detail);
  });

  test('testItemList', async function() {
    // Check the item list is filled with the data.
    await assignAndShowTestData();
    flush();

    assertTrue(isVisible(testElement.shadowRoot!.querySelector('#line')));
    assertTrue(isVisible(testElement.shadowRoot!.querySelector('#siteList')));

    const entries = getEntries();
    assertEquals(entries.length, mockData.length);

    // Check that the text describing the item is correct.
    for (let i = 0; i < mockData.length; i++) {
      assertEquals(
          mockData[i]!.origin,
          entries[i]!.querySelector(
                         '.site-representation')!.textContent!.trim());
      assertEquals(
          mockData[i]!.detail,
          entries[i]!.querySelector('.cr-secondary-text')!.textContent!.trim());
    }

    // Check the item list and line is hidden when there is no item.
    testElement.sites = [];
    flush();

    assertFalse(isVisible(testElement.shadowRoot!.querySelector('#line')));
    assertFalse(isVisible(testElement.shadowRoot!.querySelector('#siteList')));
  });

  test('testTooltip', async function() {
    // Check the item list is filled with the data.
    const text = 'Dummy tooltip text';
    await assignAndShowTestData();
    testElement.buttonIcon = 'cr20:block';
    testElement.buttonAriaLabelId =
        'safetyCheckNotificationPermissionReviewDontAllowAriaLabel';
    testElement.buttonTooltipText = text;
    flush();

    // Check that the tooltip is not visible.
    let tooltip = testElement.shadowRoot!.querySelector('cr-tooltip');
    assertTrue(!!tooltip);
    assertFalse(isVisible(tooltip));

    // User focuses the button of the 2nd item in the list.
    const item = getEntries()[1]!;
    const button = item.querySelector('cr-icon-button');
    assertTrue(!!button);
    button.focus();

    // Check that the tooltip gets visible with the correct text.
    tooltip = testElement.shadowRoot!.querySelector('cr-tooltip');
    assertTrue(!!tooltip);
    await waitUntilVisible(tooltip);
    assertTrue(isVisible(tooltip));
    assertEquals(text, tooltip!.textContent!.trim());
  });
});