chromium/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf_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 {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import {PauseActionSource} 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 {suppressInnocuousErrors, waitForPlayFromSelection} from './common.js';
import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';

suite('ReadAloud_UpdateContentSelectionPDF', () => {
  let app: AppElement;
  let testBrowserProxy: TestColorUpdaterBrowserProxy;


  // Similar to read_aloud_update_content_selection, but this tree uses
  // negative values, as some node ids, such as those for PDFs, may be negative.
  // root htmlTag='#document' id=1
  // ++paragraph htmlTag='p' id=-2
  // ++++staticText name='Hello' id=-3
  // ++paragraph htmlTag='p' id=-4
  // ++++staticText name='World' id=-5
  // ++paragraph htmlTag='p' id=-6
  // ++++staticText name='Friend' id=-7
  // ++++staticText name='!' id=-8
  const axTree = {
    rootId: 1,
    nodes: [
      {
        id: 1,
        role: 'rootWebArea',
        htmlTag: '#document',
        childIds: [-2, -4, -6],
      },
      {
        id: -2,
        role: 'paragraph',
        htmlTag: 'p',
        childIds: [-3],
      },
      {
        id: -3,
        role: 'staticText',
        name: 'Hello',
      },
      {
        id: -4,
        role: 'paragraph',
        htmlTag: 'p',
        childIds: [-5],
      },
      {
        id: -5,
        role: 'staticText',
        name: 'World',
      },
      {
        id: -6,
        role: 'paragraph',
        htmlTag: 'p',
        childIds: [-7, -8],
      },
      {
        id: -7,
        role: 'staticText',
        name: 'Friend',
      },
      {
        id: -8,
        role: 'staticText',
        name: '!',
      },
    ],
    selection: {
      anchor_object_id: -5,
      focus_object_id: -7,
      anchor_offset: 1,
      focus_offset: 2,
      is_backward: false,
    },
  };

  setup(() => {
    suppressInnocuousErrors();
    testBrowserProxy = new TestColorUpdaterBrowserProxy();
    BrowserProxy.setInstance(testBrowserProxy);
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    // Do not call the real `onConnected()`. As defined in
    // ReadAnythingAppController, onConnected creates mojo pipes to connect to
    // the rest of the Read Anything feature, which we are not testing here.
    chrome.readingMode.onConnected = () => {};

    app = document.createElement('read-anything-app');
    document.body.appendChild(app);
    document.onselectionchange = () => {};
    chrome.readingMode.setContentForTesting(axTree, []);
  });

  suite('Before speech started', () => {
    test('inner html of container matches expected html', () => {
      assertFalse(app.speechPlayingState.isSpeechActive);
      assertFalse(app.speechPlayingState.hasSpeechBeenTriggered);
      // isSpeechTreeInitialized set in updateContent
      assertTrue(app.speechPlayingState.isSpeechTreeInitialized);
      // The expected HTML before any highlights are added.
      const expected = '<div><p>World</p><p>Friend!</p></div>';
      const innerHTML = app.$.container.innerHTML;
      assertEquals(expected, innerHTML);
    });

    test('selection in reading mode panel correct', () => {
      // Calling shadowRoot.getSelection directly is not supported in TS tests,
      // so use a helper to get the selection from the app instead.
      const selection = document.getSelection()!;
      assertTrue(selection != null);
      assertEquals('World', selection.anchorNode!.textContent);
      assertEquals('Friend', selection.focusNode!.textContent);
      assertEquals(1, selection.anchorOffset);
      assertEquals(2, selection.focusOffset);
    });

    test('container class correct', () => {
      assertEquals(
          app.$.container.className,
          'user-select-disabled-when-speech-active-false');
      assertEquals('auto', window.getComputedStyle(app.$.container).userSelect);
    });
  });

  suite('While Read Aloud playing', () => {
    setup(async () => {
      app.playSpeech();
      await waitForPlayFromSelection();
    });

    test('inner html of container matches expected html', () => {
      assertTrue(app.speechPlayingState.isSpeechActive);
      assertTrue(app.speechPlayingState.isSpeechTreeInitialized);
      // The expected HTML with the current highlights.
      const expected = '<div><p><span class="parent-of-highlight">' +
          '<span class="current-read-highlight">World</span>' +
          '</span></p><p><span class="parent-of-highlight">' +
          '<span class="current-read-highlight">Friend' +
          '</span></span><span class="parent-of-highlight">' +
          '<span class="current-read-highlight">!</span>' +
          '</span></p></div>';
      const innerHTML = app.$.container.innerHTML;
      assertEquals(expected, innerHTML);
    });

    test('selection in reading mode panel cleared', () => {
      const selection = document.getSelection()!;
      assertEquals('', selection.toString());
    });

    test('container class correct', () => {
      assertEquals(
          app.$.container.className,
          'user-select-disabled-when-speech-active-true');
      assertEquals('none', window.getComputedStyle(app.$.container).userSelect);
    });
  });

  suite('While Read Aloud paused', () => {
    setup(async () => {
      app.playSpeech();
      await waitForPlayFromSelection();
      app.stopSpeech(PauseActionSource.BUTTON_CLICK);
    });
    test('inner html of container matches expected html', () => {
      assertFalse(app.speechPlayingState.isSpeechActive);
      assertTrue(app.speechPlayingState.isSpeechTreeInitialized);
      // The expected HTML with the current highlights.
      const expected = '<div><p><span class="parent-of-highlight">' +
          '<span class="current-read-highlight">World</span>' +
          '</span></p><p><span class="parent-of-highlight">' +
          '<span class="current-read-highlight">Friend' +
          '</span></span><span class="parent-of-highlight">' +
          '<span class="current-read-highlight">!</span>' +
          '</span></p></div>';
      const innerHTML = app.$.container.innerHTML;
      assertEquals(expected, innerHTML);
    });

    test('selection in reading mode panel cleared', () => {
      const selection = document.getSelection()!;
      assertEquals('', selection.toString());
    });

    test('container class correct', () => {
      assertEquals(
          app.$.container.className,
          'user-select-disabled-when-speech-active-false');
      assertEquals('auto', window.getComputedStyle(app.$.container).userSelect);
    });
  });
});