chromium/chrome/test/data/webui/side_panel/read_anything/update_content_selection_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 {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';

import {suppressInnocuousErrors} from './common.js';
import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';

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

  // 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
  // ++++link htmlTag='a' id=9
  // +++++staticText name='You've Got a Friend in Me' id=10
  const inlineId = 10;
  const inlineText = 'You\'ve Got a Friend in Me';
  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, 9],
      },
      {
        id: 7,
        role: 'staticText',
        name: 'Friend',
      },
      {
        id: 8,
        role: 'staticText',
        name: '!',
      },
      {
        id: 9,
        role: 'link',
        htmlTag: 'a',
        display: 'inline',
        childIds: [inlineId],
      },
      {
        id: inlineId,
        role: 'staticText',
        name: inlineText,
      },
    ],
  };

  function setSelection(selection: Object, contentNodes: number[]) {
    const selectedTree = Object.assign({selection: selection}, axTree);
    chrome.readingMode.setContentForTesting(selectedTree, contentNodes);
  }

  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);
  });

  test('forward selection inside distilled content', async () => {
    const expected = '<div><p>Hello</p><p>World</p><p>Friend!' +
        '<a>You\'ve Got a Friend in Me</a></p></div>';
    setSelection(
        {
          anchor_object_id: 5,
          focus_object_id: 7,
          anchor_offset: 1,
          focus_offset: 2,
          is_backward: false,
        },
        [2, 4, 6]);

    const selection = app.getSelection();

    assertEquals(expected, app.$.container.innerHTML);
    assertEquals('World', selection.anchorNode.textContent);
    assertEquals('Friend', selection.focusNode.textContent);
    assertEquals(1, selection.anchorOffset);
    assertEquals(2, selection.focusOffset);
  });

  test('selection completely outside distilled content', () => {
    const expected = '<div><p>World</p><p>Friend!' +
        '<a>You\'ve Got a Friend in Me</a></p></div>';
    setSelection(
        {
          anchor_object_id: 5,
          focus_object_id: 7,
          anchor_offset: 1,
          focus_offset: 2,
          is_backward: false,
        },
        /* contentNodes= */[]);

    const selection = app.getSelection();

    assertEquals(expected, app.$.container.innerHTML);
    assertEquals('World', selection.anchorNode.textContent);
    assertEquals('Friend', selection.focusNode.textContent);
    assertEquals(1, selection.anchorOffset);
    assertEquals(2, selection.focusOffset);
  });

  test('selection partially outside distilled content', () => {
    const expected = '<div><p>Hello</p><p>World</p><p>Friend!' +
        '<a>You\'ve Got a Friend in Me</a></p></div>';
    setSelection(
        {
          anchor_object_id: 3,
          focus_object_id: 7,
          anchor_offset: 1,
          focus_offset: 2,
          is_backward: false,
        },
        [2, 4]);

    const selection = app.getSelection();

    assertEquals(expected, app.$.container.innerHTML);
    assertEquals('Hello', selection.anchorNode.textContent);
    assertEquals('Friend', selection.focusNode.textContent);
    assertEquals(1, selection.anchorOffset);
    assertEquals(2, selection.focusOffset);
  });

  test('backward selection inside distilled content', () => {
    const expected = '<div><p>Hello</p><p>World</p><p>Friend!' +
        '<a>You\'ve Got a Friend in Me</a></p></div>';
    setSelection(
        {
          anchor_object_id: 7,
          focus_object_id: 3,
          anchor_offset: 2,
          focus_offset: 1,
          is_backward: true,
        },
        [2, 4, 6]);

    const selection = app.getSelection();

    assertEquals(expected, app.$.container.innerHTML);
    assertEquals('Hello', selection.anchorNode.textContent);
    assertEquals('Friend', selection.focusNode.textContent);
    assertEquals(1, selection.anchorOffset);
    assertEquals(2, selection.focusOffset);
  });

  test('forward selection with inline text', () => {
    setSelection(
        {
          anchor_object_id: inlineId,
          focus_object_id: inlineId,
          anchor_offset: 3,
          focus_offset: 10,
          is_backward: false,
        },
        [2, 4, 6]);

    const selection = app.getSelection();

    assertEquals(inlineText, selection.anchorNode.textContent);
    assertEquals(inlineText, selection.focusNode.textContent);
    assertEquals(3, selection.anchorOffset);
    assertEquals(10, selection.focusOffset);
  });

  test('backward selection with inline text', () => {
    setSelection(
        {
          anchor_object_id: inlineId,
          focus_object_id: inlineId,
          anchor_offset: 10,
          focus_offset: 3,
          is_backward: true,
        },
        [2, 4, 6]);

    const selection = app.getSelection();

    assertEquals(inlineText, selection.anchorNode.textContent);
    assertEquals(inlineText, selection.focusNode.textContent);
    assertEquals(3, selection.anchorOffset);
    assertEquals(10, selection.focusOffset);
  });
});