chromium/chrome/test/data/webui/side_panel/read_anything/font_menu_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 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';

import {BrowserProxy} from '//resources/cr_components/color_change_listener/browser_proxy.js';
import type {CrIconButtonElement} from '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
import {flush} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {ToolbarEvent} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import type {ReadAnythingToolbarElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';

import {getItemsInMenu, stubAnimationFrame, suppressInnocuousErrors} from './common.js';
import {FakeReadingMode} from './fake_reading_mode.js';
import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';

suite('FontMenu', () => {
  let testBrowserProxy: TestColorUpdaterBrowserProxy;
  let toolbar: ReadAnythingToolbarElement;
  let menuButton: CrIconButtonElement|null;
  let fontSelect: HTMLSelectElement|null;
  let fontEmitted: boolean;

  setup(() => {
    suppressInnocuousErrors();
    testBrowserProxy = new TestColorUpdaterBrowserProxy();
    BrowserProxy.setInstance(testBrowserProxy);
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    const readingMode = new FakeReadingMode();
    chrome.readingMode = readingMode as unknown as typeof chrome.readingMode;
    chrome.readingMode.supportedFonts = [];
    fontEmitted = false;
    document.addEventListener(ToolbarEvent.FONT, () => fontEmitted = true);
  });

  function createToolbar(): void {
    toolbar = document.createElement('read-anything-toolbar');
    document.body.appendChild(toolbar);
    flush();
    menuButton =
        toolbar.shadowRoot!.querySelector<CrIconButtonElement>('#font');
    fontSelect =
        toolbar.shadowRoot!.querySelector<HTMLSelectElement>('#font-select');
  }

  function assertFontsEqual(actual: string, expected: string): void {
    assertEquals(
        expected.trim().toLowerCase().replaceAll('"', ''),
        actual.trim().toLowerCase().replaceAll('"', ''));
  }

  suite('with read aloud', () => {
    let fontMenuOptions: HTMLButtonElement[];

    setup(() => {
      chrome.readingMode.isReadAloudEnabled = true;
      createToolbar();
    });

    function updateFonts(supportedFonts: string[]): void {
      chrome.readingMode.supportedFonts = supportedFonts;
      toolbar.updateFonts();
      fontMenuOptions = getItemsInMenu(toolbar.$.fontMenu);
    }

    test('is dropdown menu', () => {
      stubAnimationFrame();

      menuButton!.click();
      flush();

      assertTrue(toolbar.$.fontMenu.get().open);
    });

    test('shows only supported fonts', () => {
      updateFonts(['font 1', 'font 2', 'font 3', 'font 4']);
      assertEquals(fontMenuOptions.length, 4);

      updateFonts(['font 1']);
      assertEquals(1, fontMenuOptions.length);

      // initial-count in the dom-repeat for the fonts menu limits the
      // size of the font menu, so adding more than 8 fonts is difficult to
      // test. If more than 8 fonts are added on the actual menu, we can
      // increase the initial-count.
      updateFonts([
        'font 1',
        'font 2',
        'font 3',
        'font 4',
        'font 5',
        'font 6',
        'font 7',
        'font 8',
      ]);
      assertEquals(8, fontMenuOptions.length);
    });

    test('uses the first font if font not available', () => {
      // Set the current font to one that will be removed
      const fonts = ['Andika', 'Poppins', 'STIX Two Text'];
      chrome.readingMode.fontName = 'EB Garamond';
      updateFonts(fonts.concat(chrome.readingMode.fontName));

      // Update the fonts to exclude the previously chosen font
      updateFonts(fonts);

      const checkMarks = toolbar.$.fontMenu.get().querySelectorAll<HTMLElement>(
          '.check-mark-hidden-false');
      const hiddenCheckMarks =
          toolbar.$.fontMenu.get().querySelectorAll<HTMLElement>(
              '.check-mark-hidden-true');
      assertEquals(1, checkMarks.length);
      assertEquals(2, hiddenCheckMarks.length);
      assertEquals(fonts[0], chrome.readingMode.fontName);
      assertEquals(fonts[0], toolbar.style.fontFamily);
    });

    test('each font option is styled with the font that it is', () => {
      updateFonts(['Serif', 'Andika', 'Poppins', 'STIX Two Text']);
      toolbar.setFontsLoaded();
      fontMenuOptions.forEach(option => {
        assertFontsEqual(option.style.fontFamily, option.innerText);
      });
    });

    test('each font option is loading', () => {
      updateFonts(['Serif', 'Andika', 'Poppins', 'STIX Two Text']);
      fontMenuOptions.forEach(option => {
        assertFontsEqual(
            option.style.fontFamily + '\u00A0(loading)', option.innerText);
      });
    });

    suite('on font option clicked', () => {
      setup(() => {
        updateFonts(['Serif', 'Poppins', 'STIX Two Text']);
        menuButton!.click();
      });

      test('propagates font', () => {
        fontMenuOptions.forEach((option, index) => {
          fontEmitted = false;
          option.click();
          const expectedFont = chrome.readingMode.supportedFonts[index]!;
          assertFontsEqual(chrome.readingMode.fontName, expectedFont);
          assertTrue(fontEmitted);
        });
      });

      test('updates toolbar font', () => {
        fontMenuOptions.forEach((option, index) => {
          option.click();
          assertFontsEqual(
              toolbar.style.fontFamily,
              chrome.readingMode.supportedFonts[index]!);
        });
      });

      test('closes menu', () => {
        fontMenuOptions.forEach((option) => {
          option.click();
          assertFalse(toolbar.$.fontMenu.get().open);
        });
      });
    });
  });

  suite('without read aloud', () => {
    setup(() => {
      chrome.readingMode.isReadAloudEnabled = false;
      createToolbar();
    });

    function updateFonts(supportedFonts: string[]): void {
      chrome.readingMode.supportedFonts = supportedFonts;
      toolbar.updateFonts();
      flush();
    }

    test('is select menu', () => {
      assertTrue(!!fontSelect);
      assertFalse(!!menuButton);
    });

    test('shows only supported fonts', () => {
      updateFonts(['font 1', 'font 2', 'font 3', 'font 4']);
      assertEquals(
          chrome.readingMode.supportedFonts.length, fontSelect!.options.length);
    });

    test('uses the first font if font not available', () => {
      // Set the current font to one that will be removed
      const fonts = ['Andika', 'Poppins', 'STIX Two Text'];
      chrome.readingMode.fontName = 'EB Garamond';
      updateFonts(fonts.concat(chrome.readingMode.fontName));

      // Update the fonts to exclude the previously chosen font
      updateFonts(fonts);

      assertEquals(0, fontSelect!.selectedIndex);
      assertEquals(fonts[0], chrome.readingMode.fontName);
      assertEquals(fonts[0], toolbar.style.fontFamily);
    });

    suite('on font option clicked', () => {
      setup(() => {
        updateFonts(['Serif', 'Poppins', 'STIX Two Text']);
      });

      test('propagates font', () => {
        chrome.readingMode.supportedFonts.forEach((expectedFont, index) => {
          fontEmitted = false;
          fontSelect!.selectedIndex = index;
          fontSelect!.dispatchEvent(new Event('change'));
          assertFontsEqual(chrome.readingMode.fontName, expectedFont);
          assertTrue(fontEmitted);
        });
      });

      test('updates toolbar font', () => {
        chrome.readingMode.supportedFonts.forEach((supportedFont, index) => {
          fontSelect!.selectedIndex = index;
          fontSelect!.dispatchEvent(new Event('change'));
          assertFontsEqual(toolbar.style.fontFamily, supportedFont);
        });
      });
    });
  });
});