chromium/chrome/test/data/webui/cr_elements/cr_icon_test.ts

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

import '//resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
import '//resources/cr_elements/cr_icon/cr_icon.js';
import '//resources/cr_elements/cr_icon/cr_iconset.js';
import '//resources/cr_elements/icons.html.js';
import './cr_icon_instrumented.js';

import type {CrIconElement} from '//resources/cr_elements/cr_icon/cr_icon.js';
import {getTrustedHTML} from '//resources/js/static_types.js';
import {html} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertNotReached, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {microtasksFinished} from 'chrome://webui-test/test_util.js';

suite('cr-icon', function() {
  let icon: CrIconElement;

  suiteSetup(function() {
    // Add a test cr-iconset to the page. Necessary since there are not yet
    // any cr-iconsets in prod that can be imported instead.
    const div = document.createElement('div');
    div.innerHTML = getTrustedHTML`
      <cr-iconset name="cr-test" size="24">
        <svg>
          <defs>
            <g id="arrow-drop-up">
              <path d="M7 14l5-5 5 5z"></path>
            </g>
            <g id="arrow-drop-down">
              <path d="M7 10l5 5 5-5z"></path>
            </g>
          </defs>
        </svg>
      </cr-iconset>`;
    document.head.appendChild(div.querySelector('cr-iconset')!);
  });

  setup(async () => {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    icon = document.createElement('cr-icon');
    document.body.appendChild(icon);
    await microtasksFinished();
  });

  function assertSvgPath(svg: SVGElement, expectedPath: string) {
    const iconInternal = svg.querySelector('g');
    assertTrue(!!iconInternal);
    const path = iconInternal.querySelector('path');
    assertTrue(!!path);
    assertEquals(expectedPath, path.getAttribute('d'));
  }

  // Tests that cr-icons can successfully update using iron-iconset.
  test('iron-iconset', async () => {
    icon.icon = 'cr:chevron-left';
    await microtasksFinished();
    let svgs = icon.shadowRoot!.querySelectorAll('svg');
    assertEquals(1, svgs.length);
    assertSvgPath(svgs[0]!, 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z');

    icon.icon = 'cr:chevron-right';
    await microtasksFinished();
    svgs = icon.shadowRoot!.querySelectorAll('svg');
    assertEquals(1, svgs.length);
    assertSvgPath(svgs[0]!, 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z');
  });

  test('cr-iconset', async () => {
    icon.icon = 'cr-test:arrow-drop-up';
    await microtasksFinished();
    let svgs = icon.shadowRoot!.querySelectorAll('svg');
    assertEquals(1, svgs.length);
    assertSvgPath(svgs[0]!, 'M7 14l5-5 5 5z');

    icon.icon = 'cr-test:arrow-drop-down';
    await microtasksFinished();
    svgs = icon.shadowRoot!.querySelectorAll('svg');
    assertEquals(1, svgs.length);
    assertSvgPath(svgs[0]!, 'M7 10l5 5 5-5z');
  });

  test('cr-iconset used rather than iron-iconset', async () => {
    // Add an iron-iconset to the document.
    const template = html`<iron-iconset-svg name="cr20-test" size="20">
      <svg>
        <defs>
          <g id="arrow">
            <path d="M7 10l5 5 5-5z"></path>
          </g>
        </defs>
      </svg>
    </iron-iconset-svg>`;
    document.head.appendChild(template.content);

    // Add a cr-iconset with the same name.
    const div = document.createElement('div');
    div.innerHTML = getTrustedHTML`
      <cr-iconset name="cr20-test" size="20">
        <svg>
          <defs>
            <g id="arrow">
              <path d="M7 14l5-5 5 5z"></path>
            </g>
          </defs>
        </svg>
      </cr-iconset>`;
    document.head.appendChild(div.querySelector('cr-iconset')!);

    icon.icon = 'cr20-test:arrow';
    await microtasksFinished();
    const svg = icon.shadowRoot!.querySelector('svg');
    assertTrue(!!svg);

    // Confirm the cr-iconset value.
    assertSvgPath(svg, 'M7 14l5-5 5 5z');
  });

  test('ThrowsErrorOnUnknownIconset', async () => {
    // Using a subclass of cr-icon which is instrumented to report errors in the
    // updated() lifecycle callback method, for the purposes of this test. It is
    // still exercising the relevant codepath in cr-icon itself.
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    const element = document.createElement('cr-icon-instrumented');
    document.body.appendChild(element);

    async function assertThrows(icon: string) {
      element.icon = icon;
      await microtasksFinished();

      try {
        await element.updatedComplete;
        assertNotReached('Should have thrown');
      } catch (e: any) {
        assertEquals(
            `Assertion failed: Could not find iconset for: '${icon}'`,
            (e as Error).message);
      }
    }

    // Check that errors are repored as expected.
    await assertThrows('does-not-exist:foo');
    await assertThrows('does-not-exist:bar');

    // Check that existing icons still work.
    element.icon = 'cr:chevron-right';
    await microtasksFinished();
    await element.updatedComplete;
  });
});